0x03 Ensamblador ZX Spectrum Marciano – Área de juego

En este capítulo de Ensamblador ZX Spectrum Marciano, vamos a pintar el área de juego.

Creamos la carpeta Paso03 y copiamos los archivos Const.asm, Graph.asm, Main.asm y Var.asm desde la carpeta Paso02.

Antes de comenzar a pintar el área de juego es necesario saber que la pantalla del ZX Spectrum se divide en dos zonas, la parte superior con veintidós líneas (de la cero a la veintiuna), y la parte inferior (la línea de comandos).

Si cargáis el programa resultante del capítulo anterior, una vez que se ejecuta, si pulsamos la tecla ENTER se muestra el listado del cargador. Si ejecutáis la línea 40 (RUN 40), se deberían volver a pintar nuestros gráficos, pero no es así. En realidad si se pintan, pero se pintan en la línea de comandos; si estáis atentos veréis como se pintan y luego desaparecen.

Tras ejecutar nuestro programa, la parte de la pantalla activa es la línea de comandos, así que necesitamos un mecanismo para activar la parte de la pantalla en dónde queramos pintar.

Cambiando la pantalla activa

La parte superior de la pantalla es la dos, y la inferior es la uno. En la ROM hay una rutina que activa uno u otro canal, dependiendo del valor que haya en el registro A.

Abrimos el archivo Const.asm y añadimos la líneas siguientes:

; ----------------------------------------------------------------------------
; Rutina de la ROM que abre el canal de la pantalla.
;
; Entrada: A -> Canal	1 = línea de comandos 
; 				        2 = pantalla superior
; ----------------------------------------------------------------------------
OPENCHAN:		EQU $1601

A continuación, abrimos el archivo Main.asm y justo debajo de la etiqueta Main añadimos las líneas siguientes:

ld		a, $02
call	OPENCHAN

Cargamos en A el canal que queremos activar, LD A, $02, y luego llamamos a la rutina de la ROM para activarlo, CALL OPENCHAN.

Compilamos, cargamos en el emulador, pulsamos cualquier tecla, ejecutamos la línea 40 (RUN 40) y ahora sí se vuelven a pintar nuestros gráficos en el lugar correcto.

Pintamos cadenas de texto

Al trabajar con UDG, podríamos decir que pintamos caracteres, y como tal vamos a pintar la pantalla de juego.

Lo primero que vamos a implementar es una rutina que pinta cadenas de caracteres, indicando la dirección de memoria de la cadena y la longitud de la misma.

Creamos un nuevo archivo, Print.asm, e implementamos la rutina PrintString, que recibe en HL la dirección de la cadena y en B la longitud de la misma. Esta rutina altera el valor de los registros AF, B y HL.

PrintString:
ld      a, (hl)
rst     $10
inc     hl
djnz    PrintString
ret

Carga en A el carácter a pintar, LD A, (HL), pinta el carácter, RST $10, apunta HL al siguiente carácter, INC HL, y repite la operación hasta que B valga 0, DJNZ PrintString. Finalmente, sale, RET.

El aspecto final de la rutina, una vez comentada, es el siguiente:

; -----------------------------------------------------------------------------
; Pinta cadenas.
;
; Entrada:  HL = primera posición de memoria de la cadena
;           B  = longitud de la cadena.
; Altera el valor de los registros AF, B y HL
; -----------------------------------------------------------------------------
PrintString:
ld      a, (hl)         ; Carga en A el carácter a pintar
rst     $10             ; Pinta el carácter
inc     hl              ; Apunta HL al siguiente carácter
djnz    PrintString     ; Hasta que B valga 0
ret

El siguiente paso es probar, así que vamos a editar el archivo Main.asm. Lo primero, para que no se nos olvide, es incluir el archivo Print.asm antes END Main:

include	"Print.asm"

Justo antes de los includes, vamos a definir una cadena con dos etiquetas, la de la propia cadena y una segunda etiqueta para marcar el final de la misma.

Cadena:
db 'Hola Mundo'
Cadena_Fin:
db $

Justo antes del RET que nos saca al Basic, vamos a añadir la llamada a la nueva rutina.

ld      hl, Cadena
ld     	b, Cadena_Fin - Cadena
call	PrintString

Compilamos, cargamos en el emulador y vemos los resultados.

Ensamblador ZX Spectrum, área de juego
Ensamblador ZX Spectrum, área de juego

Como se puede apreciar, se ha pintado la cadena Hola Mundo a continuación de nuestros gráficos.

Vamos a añadir unos caracteres antes de la cadena, y la vamos a dejar como sigue:

db $16, $0a, $0a, 'Hola Mundo'

Volvemos compilar, cargamos en el emulador y vemos el resultado.

Ensamblador ZX Spectrum, área de juego
Ensamblador ZX Spectrum, área de juego

Como se puede observar, la cadena Hola Mundo aparece más centrada, lo que hemos conseguido con los caracteres que hemos añadido por delante de la cadena, en concreto $16 que es el carácter de control de AT (instrucción Basic para posicionar el cursor), y las coordenadas Y y X.

A continuación se muestra una lista de los caracteres de control que podemos usar al pintar las cadenas de esta manera, y los parámetros que hay que enviar.

CarácterCódigoParámetrosValores
DELETE$0c

ENTER$0d

INK$10ColorDe $00 a $07
PAPER$11ColorDe $00 a $07
FLASH$12No/SíDe $00 a $01
BRIGHT$13No/SíDe $00 a $01
INVERSE$14No/SíDe $00 a $01
OVER$15No/SíDe $00 a $01
AT$16Coordenadas Y y XY = de $00 a $15
X = de $00 a $1f
TAB$17Número de tabulaciones
Ensamblador ZX Spectrum, caracteres de control cadenas

Es muy importante que todos los códigos de control vayan seguidos de sus parámetros, para evitar resultados no deseados. Así mismo, si se imprime una cadena después de TAB, hay que añadir un espacio en blanco como primer carácter de la cadena.

Como ejercicio, probad distintas combinaciones con los caracteres de control, probad a darle color, parpadeo, etc.

Pintamos la pantalla de juego

La pantalla de juego esta bordeada por un marco, pero antes de pintar nada vamos a limpiar el archivo Main.asm, quitando todo lo que nos sobra; borramos desde las dos líneas anteriores a la etiqueta Loop, hasta la línea anterior de la instrucción RET, también borramos la definición de Cadena y Cadena_Fin.

El aspecto de Main.asm debe ser el siguiente:

org	$5dad

Main:
ld      a, $02
call    OPENCHAN
ld      hl, udgsCommon
ld      (UDG), hl
ret

include "Const.asm"
include "Var.asm"
include "Graph.asm"
include "Print.asm"

end     Main

Ahora vamos a definir en Var.asm las cadenas necesarias para pintar el marco. La vamos a incluir antes de udgsCommon.

; ----------------------------------------------------------------------------
; Marco de la pantalla
; ----------------------------------------------------------------------------
frameTopGraph:
db $16, $00, $00, $10, $01
db $96, $97, $97, $97, $97, $97, $97, $97, $97, $97, $97, $97, $97, $97, $97, $97, $97, $97, $97, $97, $97, $97, $97, $97, $97, $97, $97, $97, $97, $97, $97, $98
frameBottomGraph:
db $16, $15, $00
db $9b, $9c, $9c, $9c, $9c, $9c, $9c, $9c, $9c, $9c, $9c, $9c, $9c, $9c, $9c, $9c, $9c, $9c, $9c, $9c, $9c, $9c, $9c, $9c, $9c, $9c, $9c, $9c, $9c, $9c, $9c, $9d
frameEnd:

En la primera línea DB definimos la posición de la parte de arriba, $16, $00, $00 y la tinta, $10, $01.

En la siguiente línea definimos la parte superior del marco, primero la esquina superior izquierda, $96, luego treinta posiciones de horizontal superior, $97, y por último la esquina superior derecha, $98; todos estos números los pusimos en los comentarios de la definición de los gráficos.

En la siguiente línea definimos la posición de la parte de abajo, $16, $15, $00.

En la siguiente línea definimos la parte inferior del marco, primero la esquina inferior izquierda, $9b, luego treinta posiciones de la horizontal inferior, $9c, y por último la esquina inferior derecha, $9d.

Vamos a ver como se pinta todo esto. Volvemos a Main.asm y justo encima del RET, incluimos las líneas siguientes:

ld		hl, frameTopGraph
ld		b, frameEnd - frameTopGraph
call	PrintString

Compilamos, cargamos en el emulador y vemos los resultados.

Ensamblador ZX Spectrum, área de juego
Ensamblador ZX Spectrum, área de juego

Hemos pintado la parte superior del marco y la inferior, aunque el marco no está completo ya que faltan los laterales.

Vamos a implementar una rutina que imprima el marco, y lo vamos a hacer en Print.asm.

PrintFrame:
ld 		hl, frameTopGraph
ld 		b, frameBottomGraph - frameTopGraph
call 	PrintString

Cargamos en HL la dirección de memoria de la parte superior del marco, LD HL, frameTopGraph, cargamos en B la longitud, restando a la dirección de inicio de la parte inferior la dirección de inicio de la parte superior, LD B, frameBottomGraph – frameTopGraph, y llamamos a la rutina que pinta las cadenas, CALL PrintString.

ld 		hl, frameBottomGraph
ld 		b, frameEnd - frameBottomGraph
call	PrintString

Cargamos en HL la dirección de memoria de la parte inferior del marco, LD HL, frameBottomGraph, cargamos en B la longitud, restando a la dirección de memoria donde acaba la cadena la dirección de inicio de la parte inferior, LD B, frameEnd – frameBottomGraph, y llamamos a la rutina que pinta las cadenas, CALL PrintString.

Ya solo queda implementar un bucle para pintar los laterales.

ld 		b, $01
printFrame_loop:
ld 		a, $16
rst 	$10
ld 		a, b
rst 	$10
ld 		a, $00
rst 	$10
ld 		a, $99
rst 	$10

Cargamos en B la línea en la que empezamos a pintar los laterales, LD B, $01, cargamos en A el carácter de control de AT, LD A, $16, y lo “pintamos”, RST $10, cargamos en A la coordenada Y, LD A, B, y la “pintamos”, RST $10, cargamos en A la columna cero, LD A, $00, y la “pintamos”, RST $10, y por último, cargamos en A el carácter del lateral izquierdo, LD A, $99, y lo pintamos, RST $10.

Hacemos lo mismo con el lateral derecho, debido a que el código es prácticamente el mismo, solo marcamos las dos líneas que cambian.

ld 		a, $16
rst 	$10
ld		a, b
rst 	$10
ld 		a, $1f	; ¡CAMBIO!
rst 	$10
ld 		a, $9a	; ¡CAMBIO!
rst 	$10

Y llegamos a la parte final de la rutina.

inc		b
ld		a, b
cp		$15
jr		nz, printFrame_loop

ret

Apuntamos B a la siguiente línea, INC B, cargamos el valor en A, LD A, B, y comprobamos si B apunta a la línea veintiuno (línea donde se encuentra la parte inferior del marco), CP $15, y de no ser así repite el bucle hasta que llegue a la línea veintiuno, JR NZ, printFrame_loop. Una vez que B apunta a la línea veintiuno, salimos, RET.

El aspecto final de la rutina es el siguiente:

; ----------------------------------------------------------------------------
; Pinta el marco de la pantalla.
;
; Altera el valor de los registros HL, B y AF.
; ----------------------------------------------------------------------------
PrintFrame:
ld		hl, frameTopGraph 		; Carga en HL la dirección de la parte superior
ld		b, frameBottomGraph - frameTopGraph ; Carga en B la longutid
call	PrintString 			; Pinta la cadena

ld		hl, frameBottomGraph 	; Carga en HL la dirección de la parte inferior
ld		b, frameEnd - frameBottomGraph ; Carga en B la longitudd
call	PrintString 			; Pinta la cadena

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

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

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

ret

Ha llegado el momento de probar si se pinta todo el marco. Volvemos al archivo Main.asm y sustituimos estas lineas:

d		hl, frameTopGraph
ld		b, frameEnd - frameTopGraph
call	PrintString

Por esta otra:

call	PrintFrame

Compilamos, cargamos en el emulador y vemos el resultado.

Ensamblador ZX Spectrum, área de juego
Ensamblador ZX Spectrum, área de juego

Como vemos, ya hemos pintado el marco de la pantalla, pero quedan cosas por hacer; no hemos borrado la pantalla y se ven cosas que no deberían estar.

Limpiamos y coloreamos la pantalla

Muchos de los listados Basic que se pueden ver, en algún momento tienen una línea parecida a esta:

BORDER 0: INK 7: PAPER 0: CLS

Con esta línea se pone el borde de la pantalla en negro, la tinta en blanco y el fondo negro, y por último se limpia la pantalla aplicando los atributos de tinta y fondo.

Vamos a usar la ROM del ZX Spectrum para asignar tinta, fondo, limpiar la pantalla y vamos a implementar el cambio de color del borde.

Empezamos con la parte en la que limpiamos la pantalla, para lo cual abrimos el archivo Const.asm y añadimos las líneas siguientes:

; Variable de sistema donde están los atributos de color permanentes
ATTR_P:	    EQU $5c8d
; ----------------------------------------------------------------------------
; Rutina de la ROM que limpia la pantalla usando el valor que hay en ATTR_P.
; ----------------------------------------------------------------------------
CLS:        EQU $0daf

En ATTR_P se guardan los atributos permanentes de color en formato FBPPPIII, dónde F = FLASH (0/1), B = BRIGHT (0/1), PPP = PAPER (de 0 a 7) e III = INK (de 0 a 7). Por otro lado, CLS limpia la pantalla aplicando los atributos que hay en ATTR_P.

Volvemos a Main.asm y justo antes de la llamada a PrintFrame, añadimos las líneas siguientes:

ld		hl, ATTR_P
ld		(hl), $07
call	CLS

Cargamos la dirección de memoria de los atributos permanentes en HL, LD HL, ATTR_P, y ponemos FLASH a 0, BRIGHT a 0, PAPER a 0 e INK a 7, LD (HL), $07. Por último, llamamos a la rutina que limpia la pantalla, CALL CLS.

Compilamos, cargamos en el emulador y vemos los resultados.

Ensamblador ZX Spectrum, área de juego
Ensamblador ZX Spectrum, área de juego

Ahora vamos a cambiar el color al borde. Ya vimos en PorompomPong que la rutina BEEPER de la ROM cambia el color del borde, por lo que es necesario guardar los atributos en una variable de sistema; los atributos tienen el mismo formato que el visto para ATTR_P.

Volvemos al archivo Const.asm y añadimos la constante para la variable de sistema donde se guardan los atributos del borde.

; Variable de sistema donde se guarda el borde. También usada por BEEPER.
; También se guardan aquí los atributos de la línea de comandos.
BORDCR:	    EQU $5c48

Y ahora volvemos a Main.asm y ponemos el borde en negro, justo debajo de CALL CLS.

xor		a
out		($fe), a
ld		a, (BORDCR)
and		$c7
or		$07
ld		(BORDCR), a

Ponemos A a cero, XOR A, y el borde en negro, OUT ($FE), A. A continuación, cargamos el valor de BORDCR en A, LD A, (BORDCR), desechamos el color del borde y así lo ponemos en negro = 0, AND $C7, ponemos la tinta en blanco, OR $07, y cargamos el valor en BORDCR, LD (BORDCR), A.

La instrucción OR $07 solo es necesaria mientras volvamos al Basic, recordemos que en BORDCR están los atributos de color de la línea de comandos y si no cambiamos la tinta a blanca, se queda como originalmente está, en negro, igual que el color que le hemos dado al fondo.

Compilamos, cargamos en el emulador y vemos los resultados.

Ensamblador ZX Spectrum, área de juego
Ensamblador ZX Spectrum, área de juego

Ya solo nos queda pintar el área de información de la partida, cosa que haremos en la línea de comandos.

Pintamos la información de la partida

Lo primero es definir la línea de título del área de información de la partida, lo que vamos a hacer al inicio de Var.asm.

; ----------------------------------------------------------------------------
; Título de información de la partida
; ----------------------------------------------------------------------------
infoGame:
db  $10, $03, $16, $00, $00
db 'Vidas   Puntos   Nivel  Enemigos'
infoGame_end:

En la primera línea ponemos la tinta en magenta y posicionamos el cursor en las coordenadas 0,0. En la línea siguiente definimos los títulos de la información.

Vamos implementar en Print.asm la rutina que pinta los títulos de la información.

PrintInfoGame:
ld		a, $01
call	OPENCHAN

Como los títulos de la información se pintan en la línea de comandos, lo primero que hay que hacer es activar el canal uno. Cargamos uno en A, LD A, $01, y llamamos al cambio de canal, CALL OPENCHAN.

ld		hl, infoGame
ld		b, infoGame_end - infoGame
call	PrintString

Cargamos en HL la dirección del mensaje de información, LD HL, infoGame, cargamos en B la longitud del mensaje, LD B, infoGame_end – infoGame, y llamamos a pintar la cadena, CALL PrintString.

ld		a, $02
call	OPENCHAN

ret

Por último, volvemos a activar el canal dos (la pantalla superior), y salimos.

El aspecto final de la rutina es el siguiente:

; ----------------------------------------------------------------------------
; Pinta los títulos de información de la partida.
; Altera el valor de los registros A, C y HL.
; ----------------------------------------------------------------------------
PrintInfoGame:
ld		a, $01			; Carga 1 en A
call	OPENCHAN		; Activa el canal 1, línea de comando
ld		hl, infoGame	; Carga la dirección de la cadena de títulos en HL
ld		b, infoGame_end - infoGame	; Carga la longitud en B
call	PrintString		; Pinta la cadena de títulos
ld		a, $02			; Carga 2 en A
call	OPENCHAN		; Activa el canal 2, pantalla superior

ret

Ahora volvemos a Main.asm y justo después de CALL PrintFrame, añadimos la llamada para pintar los títulos de información de partida.

call	PrintInfoGame

Si ahora mismo compiláramos, probad si queréis, da la sensación de que lo último que hemos implementado no funciona, no pinta los títulos de información. En realidad si lo hace, pero al volver al Basic, el mensaje 0 OK, 40:1 lo borra.

Para evitar esto, vamos a quedarnos en un bucle infinito; localizamos la instrucción RET que nos devuelve a Basic y la sustituimos por las líneas siguientes:

Main_loop:
jr		Main_loop

El código final de Main.asm es el siguiente:

org		$5dad

Main:
ld		a, $02
call	OPENCHAN

ld		hl, udgsCommon
ld		(UDG), hl

ld		hl, ATTR_P
ld		(hl), $07
call	CLS

xor		a
out		($fe), a
ld		a, (BORDCR)
and		$c7
or		$07
ld		(BORDCR), a

call	PrintFrame
call	PrintInfoGame

Main_loop:
jr		Main_loop

include	"Const.asm"
include	"Var.asm"
include	"Graph.asm"
include	"Print.asm"

end		Main

Ahora sí, compilamos, cargamos en el emulador y vemos el resultado.

Ensamblador ZX Spectrum, área de juego
Ensamblador ZX Spectrum, área de juego

No sé que os parece a vosotros, pero a mí, que los títulos estén tan pegados al marco, no me gusta. Dado que en la línea de comandos solo disponemos de dos líneas sin que haga scroll, y debajo de la línea de títulos hay que pintar los datos, solo nos queda una opción, quitarle una línea al área de juego.

Vamos al archivo Var.asm, localizamos la etiqueta frameBottomGraph, y justo en la línea de abajo vemos el DB que posiciona el cursor; vamos a modificar esta línea para que la coordenada Y sea veinte en lugar de veintiuno.

db $16, $14, $00

Ahora tenemos que ir a Print.asm y localizar la etiqueta printFrame_loop. Este bucle se ejecuta hasta que B vale veintiuno; tenemos que modificar esta condición para que se ejecute hasta que valga veinte. Dos líneas por encima del RET de este método tenemos CP $15, esta es la línea que tenemos que modificar dejándola de la siguiente manera:

cp		$14			            ; Comprueba si está en la línea veinte

Volvemos a compilar, cargamos en el emulador y vemos los resultados.

Ensamblador ZX Spectrum, área de juego
Ensamblador ZX Spectrum, área de juego

Ensamblador ZX Spectrum, conclusión

Llegados a este punto, ya tenemos nuestro área de juego.

En el próximo capítulo incluiremos la nave y la moveremos.

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

Enlaces de interés

Ensamblador para ZX Spectrum Batalla espacial por Juan Antonio Rubio García.
Esta obra está bajo licencia de Creative Commons Reconocimiento-NoComercial-CompartirIgual 4.0 Internacional License.

Aquí puedes ver más cosas que he desarrollado para .Net, y aquí las desarrolladas en ensamblador para Z80.

Y recuerda, si lo usas no te limites a copiarlo, intenta entenderlo y adaptarlo a tus necesidades.

Deja una respuesta

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

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

Este sitio web utiliza cookies para que usted tenga la mejor experiencia de usuario. Si continúa navegando está dando su consentimiento para la aceptación de las mencionadas cookies y la aceptación de nuestra política de cookies, pinche el enlace para mayor información.plugin cookies

ACEPTAR
Aviso de cookies
A %d blogueros les gusta esto: