Descargar
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.
ACTUALIZADO 15/02/2018: Se añaden los fuentes verificados para su descarga. Ver nuevo apéndice B
ACTUALIZADO 20/09/2022: Se actualizan los fuentes, ahora ejecutables según el nuevo apéndice C
ACTUALIZADO 15/02/2018: Se añaden los fuentes verificados para su descarga. Ver nuevo apéndice B
ACTUALIZADO 20/09/2022: Se actualizan los fuentes, ahora ejecutables según el nuevo apéndice C
Texto y gráficos simples
Hola Mundo
El primer programa BASIC que los programadores novatos escriben por lo general
son estas líneas:
10 PRINT "Hola Mundo" 20 GOTO 10
Muy bien, el texto puede ser diferente. Tu primer esfuerzo pudo ser "David es
el mejor" o "Roberto estuvo aquí", pero seamos sinceros, la visualización de
texto y gráficos en pantalla es probablemente el aspecto más importante de escribir
cualquier juego de ordenador y (con la excepción de las máquinas de pinball
o las tragaperras) es prácticamente imposible concebir un juego sin pantalla.
Con esto en mente, vamos a empezar este tutorial con algunas de las rutinas
de pantalla más importantes de la ROM del Spectrum.
Entonces, ¿cómo hacemos para convertir el programa BASIC anterior a código máquina?
Bueno, podemos imprimir mediante el uso de la instrucción RST 16, lo que efectivamente
es lo mismo que PRINT CHR$ a solo que simplemente imprime el carácter
que ocupa el acumulador en el canal actual. Para imprimir una cadena en la pantalla,
tenemos que llamar a dos rutinas: una para abrir la pantalla superior para la
impresión (canal 2), y una segunda para imprimir la cadena. La rutina en la
dirección ROM 5633 abrirá el número de canal que pasamos en el acumulador, y
la de 8252 imprimirá una cadena que comienza en de con la longitud indicada
en bc por este canal. Una vez que se abre el canal 2, toda la impresión
se envía a la pantalla superior hasta que llamamos a 5633 con otro valor para
enviar la salida a otra parte. Otros canales interesantes son 1 para la pantalla
inferior (como PRINT #1 en BASIC, que podemos usar para mostrar en las
dos líneas inferiores) y el 3 para la ZX Printer.
ld a,2 ; pantalla superior. call 5633 ; abrir canal. loop ld de,string ; dirección de la cadena de caracteres. ld bc,eostr-string ; longitud de la cadena a imprimir. call 8252 ; imprimir su cadena. jp loop ; repetir hsta que se llene la pantalla. string defb '(tu nombre) es sexi' eostr equ $
La ejecución de este programa llena la pantalla con el texto hasta que aparece
scroll? en la parte inferior. Notarás sin embargo que, en lugar de que
cada línea de texto aparezca en una línea propia como en el listado, el
principio de cada cadena continúa directamente desde el final de la anterior,
que no es exactamente lo que queríamos. Para lograr esto tenemos que lanzar
un salto de línea nosotros mismos, utilizando un código de control ASCII. Una
forma de hacer esto sería cargar el acumulador con el código para una nueva
línea (13), a continuación utilizar RST 16 para imprimir ese código. Otra forma
más eficiente es agregar el código ASCII al final de nuestra cadena de este
modo:
string DEFB '(tu nombre) es sexi' DEFB 13 eostr equ $
Hay una serie de códigos de control ASCII como éste que altera la posición
de impresión actual, los colores, etc., y la experimentación te ayudará a decidir
cuáles encontrarás más útiles. Estos son los principales que utilizo:
13 (NEWLINE) Establece la posición de impresión al principio de la línea
siguiente. 16,c (INK) Establece el color de la tinta con el valor del siguiente byte. 17,c (PAPER) Ajusta el color del fondo con el valor del siguiente byte. 22,x,y (AT) Ajusta la impresión a las coordenadas x e y con los valores
especificados en los dos bytes siguientes.
El código 22 es particularmente útil para establecer las coordenadas en las que
se va a mostrar un texto o un carácter gráfico. En este ejemplo se mostrará
un signo de exclamación en la parte inferior derecha de la pantalla:
ld a,2 ; pantalla superior. call 5633 ; abrir canal. ld de,string ; dirección de la cadena. ld bc,eostr-string ; longitud de la cadena a imprimir. call 8252 ; imprimir nuestra cadena. ret string defb 22,21,31,'!' eostr equ $
Este programa va un paso más allá y anima un asterisco desde el fondo hasta
la parte superior de la pantalla:
ld a,2 ; 2 = pantalla superior. call 5633 ; abrir el canal. ld a,21 ; fila 21 = parte inferior de la pantalla. ld (xcoord),a ; establece la coordenada x inicial. loop call setxy ; actualiza nuestras coordenadas x/y. ld a,'*' ; queremos un asterisco aquí. rst 16 ; presentarlo. call delay ; esperamos un retardo. call setxy ; actualiza nuestras coordenadas x/y. ld a,32 ; código ASCII para espacio en blanco. rst 16 ; borrar el anterior asterisco. ld hl,xcoord ; posición vertical. dec (hl) ; moverla hacia arriba una línea. ld a,(hl) ; ¿donde estamos ahora? cp 255 ; ¿nos pasamos del inicio de la pantalla? jr nz,loop ; no, repetir. ret delay ld b,10 ; longitud del retardo. delay0 halt ; esperar una interrupción. djnz delay0 ; bucle. ret ; regresar. setxy ld a,22 ; código de control ASCII para AT. rst 16 ; imprimir aquí. ld a,(xcoord) ; posición vertical. rst 16 ; imprimir aquí. ld a,(ycoord) ; coordenada y. rst 16 ; imprimir aquí. ret xcoord defb 0 ycoord defb 15
Impresión de gráficos simples
Mover asteriscos alrededor de la pantalla está muy bien, pero incluso para el
juego más simple lo que realmente necesitamos es mostrar gráficos. Se hablará
de gráficos avanzados en capítulos posteriores, por ahora sólo vamos a usar
simples invasores del espacio como tipo de gráficos, y como cualquier programador
en BASIC te dirá, el Spectrum tiene un mecanismo muy simple para esto: los gráficos
definidos por el usuario, generalmente abreviados como UDG.
La tabla ASCII del Spectrum contiene 21 (19 en modo 128k) caracteres gráficos
definidos por el usuario, comenzando en el código 144 y llegando hasta el 164
(162 en modo 128k). En BASIC los UDG se definen introduciendo datos mediante
POKE en el área de UDG de la parte superior de la RAM, pero en código
de máquina tiene más sentido cambiar la variable de sistema que apunta a
la posición de memoria en la que se almacenan los UDG, lo que se realiza cambiando
el valor de dos bytes en la dirección 23675. Ahora podemos modificar nuestro
programa del asterisco en movimiento para mostrar en su lugar un gráfico con
los cambios que están subrayados.
ld hl,udgs ; UDG que definimos. ld (23675),hl ; actualizamos la variable del sistema para los UDG. ld a,2 ; 2 = pantalla superior. call 5633 ; abrir el canal. ld a,21 ; fila 21 = parte inferior de la pantalla. ld (xcoord),a ; establece la coordenada x inicial. loop call setxy ; actualiza nuestras coordenadas x/y. ld a,144 ; en lugar del asterisco aquí usamos un UDG. rst 16 ; presentarlo. call delay ; esperamos un retardo. call setxy ; actualiza nuestras coordenadas x/y. ld a,32 ; código ASCII para espacio en blanco. rst 16 ; borrar el anterior asterisco. ld hl,xcoord ; posición vertical. dec (hl) ; moverla hacia arriba una línea. ld a,(hl) ; ¿donde estamos ahora? cp 255 ; ¿nos pasamos del inicio de la pantalla? jr nz,loop ; no, repetir. ret delay ld b,10 ; longitud del retardo. delay0 halt ; esperar una interrupción. djnz delay0 ; bucle. ret ; regresar. setxy ld a,22 ; código de control ASCII para AT. rst 16 ; imprimir aquí. ld a,(xcoord) ; posición vertical. rst 16 ; imprimir aquí. ld a,(ycoord) ; coordenada y. rst 16 ; imprimir aquí. ret xcoord defb 0 ycoord defb 15 udgs defb 60,126,219,153 defb 255,255,219,219
Por supuesto, no hay razón por la que no se puedan utilizar más de 21 UDG
si se desea. Sólo hay que establecer una serie de bancos de ellos en memoria
y apuntar a cada uno cuando se necesite.
Como alternativa, se puede redefinir el conjunto de caracteres en su lugar.
Esto nos da un mayor rango de caracteres ASCII desde 32 (ESPACIO) a 127 (el símbolo
de copyright). Incluso se puede mezclar texto y gráficos, se redefinen las
letras y los números con una fuente del estilo de tu elección, y a continuación
utilizamos los símbolos y letras minúsculas para los aliens, zombies o lo que
el juego requiera. Para apuntar a otro conjunto restamos 256 de la dirección
en la que se coloca la fuente y colocamos esto en la variable de sistema de
dos bytes en la dirección 23606. El tipo de letra predeterminado de Sinclair,
por ejemplo, se encuentra en la dirección ROM 15616, por lo que la variable
de sistema en la dirección 23606 apunta a 15360 cuando el Spectrum se enciende.
Este código copia la fuente Sinclair de la ROM a la RAM y lo "pasa a negrita"
a continuación, luego establece la variable de sistema para apuntar al nuevo
juego de caracteres:
ld hl,15616 ; fuente en ROM. ld de,60000 ; dirección de su fuente. ld bc,768 ; 96 caracteres * 8 filas que alterar. font1 ld a,(hl) ; obtiene el bitmap. rlca ; lo rota hacia la izquierda. or (hl) ; combina las 2 imágenes. ld (de),a ; lo escribe en la nueva fuente. inc hl ; siguiente byte del antiguo. inc de ; siguiente byte del nuevo. dec bc ; decrementar el contador. ld a,b ; byte alto. or c ; combinar con el byte bajo. jr nz,font1 ; repetir hasta que bc=zero. ld hl,60000-256 ; fuente menos 32*8. ld (23606),hl ; apuntar a la nueva fuente. ret
Mostrando números
Para la mayoría de los juegos lo mejor para definir la puntuación del jugador
es como una cadena de dígitos ASCII, aunque eso significa más trabajo en las
rutinas de puntuación y hace que las tablas de puntuación más alta sean un verdadero
grano en el culo para un programador en lenguaje ensamblador sin experiencia.
Vamos a cubrir esto en un capítulo posterior, pero por ahora vamos a utilizar
algunas rutinas de la ROM que están muy a mano para imprimir nuestros números.
Hay dos formas de imprimir un número en la pantalla, la primera es hacer uso
de la misma rutina que la ROM utiliza para imprimir los números de línea del
Sinclair BASIC. Para esto basta con cargar el par de registros bc con el número
que desea imprimir, y luego llamar a 6683:
ld bc,(puntuación) call 6683
Sin embargo, dado que los números de línea en el BASIC sólo pueden llegar al 9999, tiene la desventaja de que sólo será capaz de mostrar un número de cuatro
dígitos. Una vez que la puntuación del jugador llega a 10000 se muestran caracteres
ASCII en lugar de números. Afortunadamente, hay otro método que llega mucho
más allá. En lugar de llamar a la rutina de visualización del número de línea,
podemos llamar a una rutina para colocar el contenido de los registros bc en
la pila de cálculo, y a continuación llamar a otra rutina que muestra el número
en la parte superior de esta pila. No te preocupes de lo que es la pila de
cálculo y cuál es su función, porque es de poca utilidad para un programador
de juegos arcade, pero ya que podemos hacer uso de ella lo haremos. Sólo
recuerda que las tres líneas siguientes mostrará un número entero del 0 al 65535
ambos inclusive:
ld bc,(puntuación) call 11563 ; apilar número en bc. call 11747 ; mostrar la cima de la pila de cálculo.
Cambio de Colores
Para establecer la tinta, el fondo, el brillo y el flash a niveles permanentes
podemos escribir directamente en la variable de sistema en 23693, a continuación
borra la pantalla con una llamada a la ROM:
; Queremos una pantalla de color amarillo. ld a,49 ; tinta azul (1) con fondo amarillo (6 * 8). ld (23693),a ; establecer nuestros colores de pantalla. call 3503 ; borrar la pantalla.
La forma más rápida y sencilla de establecer el color del borde es escribir en el puerto 254. Los 3 bits menos significativos del byte que enviamos determinan el color, por lo que para establecer el borde en rojo:
ld a,2 ; 2 es el código para el rojo. out (254),a ; escribir en el puerto 254.
El puerto 254 también maneja el altavoz y el micrófono a través de los bits
3 y 4, por tanto el efecto del borde sólo durará hasta la próxima llamada
a la rutina de sonido del altavoz en la ROM (más adelante hablaremos de esto),
por lo que es necesaria una solución más permanente. Para ello, simplemente
hay que cargar el acumulador con el color requerido y llamar a la rutina de
la ROM en 8859. Esto cambiará el color y la variable de sistema BORDCR (ubicada
en la dirección 23624) en consonancia. Para establecer un borde rojo permanente
podemos hacer esto:
ld a,2 ; 2 es el código para el rojo. call 8859 ; establecer el color del borde.
Gran trabajo José Antonio. Me viene que ni pintado!! Precisamente andaba yo en estos tiempos pensando en meterme ya en código máquina (después de más de 30 años programando en Basic;) y hace unos días me decanté definitivamente por el Z80. Sobre este tema me gustaría preguntarte acerca de un programa ensamblador que va de lujo y que se ejecuta desde el propio navegador, además puedes ejecutar-ver la pantalla del Spectrum con los resultados del código compilado. He buscado por la red pero no he encontrado nada que se le aproxime. ¿tú la conoces por casualidad?
ResponderEliminarUn abrazo.
Hay varios emuladores on-line genéricos como ASM80, y emuladores de Spectrum on-line como QAOP, JBacteria, ZSpectrumNET, puede usar uno de los primeros para desarrollar y uno de los segundos para probar, pero no es nada sencillo al ser programas diferentes. Para programar en ensamblador no son muy adecuados, mejor bájate un emulador de Spectrum que permita programar y depurar, y lo ejecutas en un PC que es mas práctico.
EliminarValioso trabajo el tuyo José Antonio. He retomado un viejo proyecto dobre el desarrollo dd un videojuego abandonado hace más de 30 años y estoy combinando sencillas rutinas en código máquina con Basic compilado. Los resultados son, al menis para alguien que en su momento estuvo limitado al Basic del Spectrum, espectaculares, y he podido resolver con solvencia los obstáculos que en su día parecían insalvables.
ResponderEliminarP.d. td recuerdo que tengo varias pocket computers averiadas para regalarte cuando pases por Alicante. Mi email es eurocamsuite@yahoo.es ya que el gmail apenas lo uso.
Con respecto a los emuladores los probaré en cuanto pueda. De momento he estado haciendo pruebas con pasmo y un editor de textos. Lo cierto es que acabo de ver tus respuestas porque publiqué los post con una cuenta gmail q no miraba desde entonces... y hoy revisando lo he visto. Muchísimas gracias José Antonio.
ResponderEliminarPero yo escribo todo lo que pones que hay que escribir y me pone errores por todos lados :(
ResponderEliminarDespués de tanto tiempo aprovecho para enviar un saludo a José Antonio y también para dejar constancia por aquí de una sencilla herramienta que me monté hace tiempo para escribir, compilar y ejecutar código ensamblador de ZX-Spectrum de forma rápida y directa. Os explico.
ResponderEliminarDEVASM-Z80 es un completo entorno IDE de desarrollo en ensamblador para la plataforma Sinclair ZX-Spectrum. El sistema integra 3 aplicaciones gratuitas/freeware que se conectan entre sí para configurar un entorno completo de edición, ensamblado, compilación y ejecución del código de una forma sencilla, cómoda e inmediata.
Más allá del ámbito meramente lúdico de la retrocomputación, el aprendizaje del código máquina constituye un reto interesante para cualquier estudiante y/o apasionado de la computación y además, ofrece un conocimiento profundo de uno de los procesadores más populares e importantes en la historia de los ordenadores y que aún está presente en multitud de dispositivos.
DEVASM-Z80 corre en cualquier sistema Windows de 32 bits y puede utilizarse libremente para el desarrollo y aprendizaje del lenguaje assembler del microprocesador de 8 bits Z80, incorporado en la familia de microordendores Sinclair ZX-Spectrum en los años 80.
https://calentamientoglobalacelerado.net/ZXopensource/taller/KIT_ENTORNO_IDE_ENSAMBLADOR_ZX.zip