cursos:ensamblador:gfx4_fuentes

Diferencias

Muestra las diferencias entre dos versiones de la página.

Enlace a la vista de comparación

Ambos lados, revisión anterior Revisión previa
Próxima revisión
Revisión previa
Última revisiónAmbos lados, revisión siguiente
cursos:ensamblador:gfx4_fuentes [08-01-2024 06:28] sromerocursos:ensamblador:gfx4_fuentes [18-01-2024 08:32] sromero
Línea 3: Línea 3:
  En prácticamente cualquier programa o juego nos encontraremos con la necesidad de imprimir en pantalla texto y datos numéricos en diferentes posiciones de pantalla: los menúes, los nombres de los niveles, los marcadores y puntuaciones, etc.  En prácticamente cualquier programa o juego nos encontraremos con la necesidad de imprimir en pantalla texto y datos numéricos en diferentes posiciones de pantalla: los menúes, los nombres de los niveles, los marcadores y puntuaciones, etc.
  
- La impresión de fuentes de texto es una aplicación directa de las rutinas de impresión de sprites en baja resolución que creamos en el capítulo anterior. Cada carácter es un sprite de 8x8 píxeles a dibujar en coordenadas (c,f) de baja resolución.+ La impresión de fuentes de texto es una aplicación directa de las rutinas de impresión de sprites en baja resolución que creamos en el capítulo anterior. Cada carácter es un sprite de 8x8 píxeles a dibujar en coordenadas (COLUMNAFILA) de baja resolución.
  
  Crearemos una rutina de impresión de caracteres y, basada en esta, una rutina de impresión de cadenas, con la posibilidad de añadir códigos de control y de formato, e impresión de datos numéricos o variables decimales, hexadecimales, binarias y de cadena.  Crearemos una rutina de impresión de caracteres y, basada en esta, una rutina de impresión de cadenas, con la posibilidad de añadir códigos de control y de formato, e impresión de datos numéricos o variables decimales, hexadecimales, binarias y de cadena.
Línea 49: Línea 49:
  Como hay un total de 96 caracteres de texto imprimibles (desde el 32 al 127), para disponer de un juego de caracteres suficiente con minúsculas, mayúsculas, signos de puntuación y dígitos nos bastaría con una fuente de 96 sprites de 8 bytes cada uno, es decir, un total de 768 bytes.  Como hay un total de 96 caracteres de texto imprimibles (desde el 32 al 127), para disponer de un juego de caracteres suficiente con minúsculas, mayúsculas, signos de puntuación y dígitos nos bastaría con una fuente de 96 sprites de 8 bytes cada uno, es decir, un total de 768 bytes.
  
- Podemos agregar "caracteres" adicionales para disponer de códigos de control que nos permitan imprimir vocales con acentos, eñes, cedilla (ç), etc. Al definir los textos de nuestro programa habría que utilizar estos códigos de control en formato numérico (DB) intercalados con el texto ASCII ("España" = DB "Espa", codigo_enye, "a").+ Podemos agregar "caracteres" adicionales para disponer de códigos de control que nos permitan imprimir vocales con acentos, eñes, cedilla (ç), etc. Al definir los textos de nuestro programa habría que utilizar estos códigos de control en formato numérico (DB) intercalados con el texto ASCII ("España"''DB "Espa", codigo_enye, "a'').
  
  Si tenemos problemas de espacio en nuestro programa también podemos utilizar un set de caracteres más reducido que acaben en el ASCII 'Z' (ASCII 90), lo que nos dejaría un charset con números, signos de puntuación y letras mayúsculas (sin minúsculas). El espacio ocupado por este spriteset sería de 90-32=58 caracteres, es decir, 464 bytes.  Si tenemos problemas de espacio en nuestro programa también podemos utilizar un set de caracteres más reducido que acaben en el ASCII 'Z' (ASCII 90), lo que nos dejaría un charset con números, signos de puntuación y letras mayúsculas (sin minúsculas). El espacio ocupado por este spriteset sería de 90-32=58 caracteres, es decir, 464 bytes.
Línea 114: Línea 114:
 ==== PrintChar_8x8 ==== ==== PrintChar_8x8 ====
  
- La rutina de impresión de caracteres debe de recoger el código ASCII a dibujar y realizar el cálculo para posicionar un puntero "origen" dentro del tileset contra el ASCII correspondiente. También debe calcular la dirección destino en la pantalla en base a las coordenadas en baja resolución. Una vez trazado el carácter, establecerá el atributo del mismo con el valor contenido en FONT_ATTRIB.+ La rutina de impresión de caracteres debe de recoger el código ASCII a dibujar y realizar el cálculo para posicionar un puntero "origen" dentro del tileset contra el ASCII correspondiente. También debe calcular la dirección destino en la pantalla en base a las coordenadas en baja resolución. Una vez trazado el carácter, establecerá el atributo del mismo con el valor contenido en ''FONT_ATTRIB''.
  
 <code z80> <code z80>
Línea 193: Línea 193:
 </code> </code>
  
- La rutina es muy parecida a las que ya vimos en el capítulo anterior para impresión de sprites 8x8, pero eliminando el cálculo del atributo origen al establecerlo desde la variable FONT_ATTRIB.+ La rutina es muy parecida a las que ya vimos en el capítulo anterior para impresión de sprites 8x8, pero eliminando el cálculo del atributo origen al establecerlo desde la variable ''FONT_ATTRIB''.
  
- Además, hemos semi-desenrollado el bucle de impresión para hacer 7 iteraciones y después escribir el último scanline fuera del bucle. De esta forma evitamos el INC DE + INC H que se realizaría para el último scanline y que es innecesario. De nuevo, desenrollar totalmente el bucle sería lo más óptimo, ya que evitaríamos el "LD B, 7y el "DJNZ".+ Además, hemos semi-desenrollado el bucle de impresión para hacer 7 iteraciones y después escribir el último scanline fuera del bucle. De esta forma evitamos el ''INC DE'' ''INC H'' que se realizaría para el último scanline y que es innecesario. De nuevo, desenrollar totalmente el bucle sería lo más óptimo, ya que evitaríamos el ''LD B, 7'' y el ''DJNZ''.
  
  La impresión se realiza por transferencia, pero podemos convertirla en impresión por operación lógica cambiando los:  La impresión se realiza por transferencia, pero podemos convertirla en impresión por operación lógica cambiando los:
Línea 212: Línea 212:
 </code> </code>
  
- Para llamar a nuestra nueva rutina establecemos los valores de impresión en las variables de memoria y realizamos el CALL correspondiente. Aunque en esta rutina podríamos utilizar el paso por registros, vamos a emplear paso de parámetros por variables de memoria por 2 motivos:+ Para llamar a nuestra nueva rutina establecemos los valores de impresión en las variables de memoria y realizamos el ''CALL'' correspondiente. Aunque en esta rutina podríamos utilizar el paso por registros, vamos a emplear paso de parámetros por variables de memoria por 2 motivos:
  
   * Para que sean utilizables desde BASIC.   * Para que sean utilizables desde BASIC.
Línea 219: Línea 219:
  Nótese que nuestro set de caracteres empieza en el ASCII 32 (espacio) el cual se corresponde con el sprite 0 (sprite en blanco). En teoría, la rutina debería restar 32 a cada código ASCII recibido en el registro A para encontrar el identificador de sprite correcto en el tileset.  Nótese que nuestro set de caracteres empieza en el ASCII 32 (espacio) el cual se corresponde con el sprite 0 (sprite en blanco). En teoría, la rutina debería restar 32 a cada código ASCII recibido en el registro A para encontrar el identificador de sprite correcto en el tileset.
  
- En lugar de realizar una resta en cada impresión, podemos establecer el valor de FONT_CHARSET a la dirección del array menos 256 (32*8), con lo que cuando se calcule la dirección origen usando el ASCII real estaremos accediendo al sprite correcto.+ En lugar de realizar una resta en cada impresión, podemos establecer el valor de ''FONT_CHARSET'' a la dirección del array menos 256 (32*8), con lo que cuando se calcule la dirección origen usando el ASCII real estaremos accediendo al sprite correcto.
  
- Así, asignando a FONT_CHARSET el valor de "charset1-256", cuando solicitemos imprimir el ASCII 32, estaremos accediendo realmente a charset1-256+(32*8) = charset1-256+256 = charset1. De esta forma no necesitamos restar 32 a ningun carácter ASCII para que la rutina imprima el carácter correspondiente real a un valor ASCII.+ Así, asignando a ''FONT_CHARSET'' el valor de "charset1-256", cuando solicitemos imprimir el ASCII 32, estaremos accediendo realmente a charset1-256+(32*8) = charset1-256+256 = charset1. De esta forma no necesitamos restar 32 a ningun carácter ASCII para que la rutina imprima el carácter correspondiente real a un valor ASCII.
  
  El código de asignación de variables iniciales y llamada a la función sería pues similar al siguiente:  El código de asignación de variables iniciales y llamada a la función sería pues similar al siguiente:
Línea 242: Línea 242:
 </code> </code>
  
- **Nótese que esta implementación de PrintChar_8x8 modifica registros y no los preserva, por lo que si tenemos que salvaguardar el valor de algún registro, debemos realizar PUSH antes de llamar a la función y POP al volver de ella**.+ **Nótese que esta implementación de ''PrintChar_8x8'' modifica registros y no los preserva, por lo que si tenemos que salvaguardar el valor de algún registro, debemos realizar ''PUSH'' antes de llamar a la función y ''POP'' al volver de ella**.
  
  El siguiente ejemplo (donde se ha omitido el código de las funciones ya vistas hasta ahora) muestra el charset 8x8 de 64 caracteres en pantalla, en un bucle de 4 líneas de 16 caracteres cada una:  El siguiente ejemplo (donde se ha omitido el código de las funciones ya vistas hasta ahora) muestra el charset 8x8 de 64 caracteres en pantalla, en un bucle de 4 líneas de 16 caracteres cada una:
Línea 248: Línea 248:
 <code z80> <code z80>
 ; Visualizacion del charset de ejemplo ; Visualizacion del charset de ejemplo
-ORG 35000+    ORG 35000
  
     LD H, 0     LD H, 0
Línea 301: Línea 301:
     JR loop     JR loop
     RET     RET
- 
  
 ;------------------------------------------------------------- ;-------------------------------------------------------------
Línea 310: Línea 309:
 ;------------------------------------------------------------- ;-------------------------------------------------------------
  
-END 35000+    END 35000
 </code> </code>
  
-Un apunte: en este caso hemos usado FONT_CHARSET como una variable DW (2 bytes) embebida en el código, pero podríamos haber aprovechado la variable del sistema CHARS para ello, cambiando el **DW 0** por **FONT_CHARSET EQU $5C36**, de forma que FONT_CHARSET apunte a CHARS, y ahorrar esos 2 bytes.+Un apunte: en este caso hemos usado ''FONT_CHARSET'' como una variable ''DW'' (2 bytes) embebida en el código, pero podríamos haber aprovechado la variable del sistema ''CHARS'' para ello, cambiando el ''FONT_CHARSET DW 0'' por ''FONT_CHARSET EQU $5C36'' (una referencia a memoria, no un espacio en ella), de forma que ''FONT_CHARSET'' apunte a ''CHARS'', y ahorrar esos 2 bytes.
  
 \\ \\
 ==== PrintString_8x8 ==== ==== PrintString_8x8 ====
  
- Nuestro siguiente objetivo es el de poder agrupar diferentes caracteres en una cadena y diseñar una función que permita imprimir toda la cadena mediante llamadas consecutivas a PrintChar_8x8.+ Nuestro siguiente objetivo es el de poder agrupar diferentes caracteres en una cadena y diseñar una función que permita imprimir toda la cadena mediante llamadas consecutivas a ''PrintChar_8x8''.
  
  Definiremos las cadenas de texto como ristras de códigos ASCII acabadas en un byte 0 (valor 0, no ASCII '0'):  Definiremos las cadenas de texto como ristras de códigos ASCII acabadas en un byte 0 (valor 0, no ASCII '0'):
Línea 399: Línea 398:
     ;;; Prueba de impresion de cadenas:     ;;; Prueba de impresion de cadenas:
  
-    LD HL, charset1-256          ; Saltamos los 32 caracteres iniciales+    LD HL, charset1-256       ; Saltamos los 32 caracteres iniciales
     LD (FONT_CHARSET), HL     LD (FONT_CHARSET), HL
     LD A, 64+3     LD A, 64+3
Línea 420: Línea 419:
 \\ \\
  
- La forma más óptima de programar la rutina de impresión consistiría en integrar el código de PrintChar_8x8 dentro de PrintString_8x8 evitando el recálculo de offset en pantalla en cada carácter. Se utilizaría DE como puntero en la fuente y HL como puntero en pantalla. Una vez calculada la posición inicial en pantalla para el primer carácter se variaría HL apropiadamente, incrementandolo en 1 para avances hacia la derecha tras la impresión de un carácter. Los retornos de carro se realizarían restando 31 a L y ejecutando //Caracter_Abajo_HL//. De esta forma se evitaría no sólo el CALL y RET contra PrintChar_8x8 sino que tampoco habría que realizar el continuo cálculo de la posición destino. Después habría que trazar los atributos repitiendo el proceso desde el inicio de la cadena.+ La forma más óptima de programar la rutina de impresión consistiría en integrar el código de ''PrintChar_8x8'' dentro de ''PrintString_8x8'' evitando el recálculo de offset en pantalla en cada carácter. Se utilizaría DE como puntero en la fuente y HL como puntero en pantalla. Una vez calculada la posición inicial en pantalla para el primer carácter se variaría HL apropiadamente, incrementandolo en 1 para avances hacia la derecha tras la impresión de un carácter. Los retornos de carro se realizarían restando 31 a L y ejecutando //Caracter_Abajo_HL//. De esta forma se evitaría no sólo el ''CALL'' ''RET'' contra ''PrintChar_8x8'' sino que tampoco habría que realizar el continuo cálculo de la posición destino. Después habría que trazar los atributos repitiendo el proceso desde el inicio de la cadena.
  
  Normalmente no se suele imprimir texto durante el desarrollo del juego (al menos no en el bucle principal de juego), por lo que puede no ser necesario llegar a este extremo de optimización a cambio de una mayor ocupación de las rutinas en memoria.  Normalmente no se suele imprimir texto durante el desarrollo del juego (al menos no en el bucle principal de juego), por lo que puede no ser necesario llegar a este extremo de optimización a cambio de una mayor ocupación de las rutinas en memoria.
Línea 426: Línea 425:
  Si vamos a utilizar la impresión de cadenas en la introducción del juego o programa, los menúes, la descripción de las fases, los créditos, la pausa o los mensajes entre nivel y nivel o el final del juego, probablemente será suficiente con la rutina que acabamos de ver.  Si vamos a utilizar la impresión de cadenas en la introducción del juego o programa, los menúes, la descripción de las fases, los créditos, la pausa o los mensajes entre nivel y nivel o el final del juego, probablemente será suficiente con la rutina que acabamos de ver.
  
- Las cadenas de texto que se puedan usar en el bucle principal del programa deberían imprimirse como Sprites, y los valores numéricos como impresión tipo "PrintChar_8x8de cada uno de sus dígitos, realizando sólo una conversión numérica del número de dígitos realmente necesario.+ Las cadenas de texto que se puedan usar en el bucle principal del programa deberían imprimirse como Sprites, y los valores numéricos como impresión tipo ''PrintChar_8x8'' de cada uno de sus dígitos, realizando sólo una conversión numérica del número de dígitos realmente necesario.
  
 \\ \\
Línea 471: Línea 470:
  La conversión de un valor numérico a una representación binaria se basa en testear el estado de cada uno de los bits del registro "parámetro" y almacenar en la cadena destino apuntada por DE un valor ASCII '0' ó '1' según el resultado del testeo.  La conversión de un valor numérico a una representación binaria se basa en testear el estado de cada uno de los bits del registro "parámetro" y almacenar en la cadena destino apuntada por DE un valor ASCII '0' ó '1' según el resultado del testeo.
  
- En lugar de ejecutar 8 ó 16 comparaciones con el comando BIT, utilizaremos la rotación a la derecha del registro parámetro de forma que el bit a comprobar sea desplazado al Carry Flag y podamos testear su valor con un **JR NC** **JR C**.+ En lugar de ejecutar 8 ó 16 comparaciones con el comando ''BIT'', utilizaremos la rotación a la derecha del registro parámetro de forma que el bit a comprobar sea desplazado al Carry Flag y podamos testear su valor con un ''JR NC'' ''JR C''.
  
  La rutina de conversión tiene 2 puntos de entrada diferentes según necesitemos convertir un número de 8 bits (valor en registro L) o de 16 bits (valor en registro HL), pero utiliza el mismo core de conversión para ambos casos:  La rutina de conversión tiene 2 puntos de entrada diferentes según necesitemos convertir un número de 8 bits (valor en registro L) o de 16 bits (valor en registro HL), pero utiliza el mismo core de conversión para ambos casos:
Línea 522: Línea 521:
 </code> </code>
  
- En el área de memoria apuntada por //conv2string// tendremos la representación binaria en ASCII del valor en L o HL, acabado en un carácter 0, lista para imprimir con PrintString_8x8.+ En el área de memoria apuntada por ''conv2string'' tendremos la representación binaria en ASCII del valor en L o HL, acabado en un carácter 0, lista para imprimir con ''PrintString_8x8''.
  
 \\ \\
Línea 683: Línea 682:
 \\ \\
  
- Finalmente, el usuario **climacus** en los foros de Speccy.org nos ofrece la siguiente variación de INC_HL_Remove_Leading_Zeros para que la rutina imprima espacios en lugar de "leading zeros", lo que provoca que el texto en pantalla esté justificado a la derecha en ocupando siempre 3 (valores de 8 bits) ó 5 (valores de 16 bits) caracteres. Esto permite que los valores impresos puedan sobreescribir en pantalla valores anteriores aunque estemos imprimiendo un valor "menor" que el que reside en pantalla:+ Finalmente, el usuario **climacus** en los foros de Speccy.org nos ofrece la siguiente variación de ''INC_HL_Remove_Leading_Zeros'' para que la rutina imprima espacios en lugar de "leading zeros", lo que provoca que el texto en pantalla esté justificado a la derecha en ocupando siempre 3 (valores de 8 bits) ó 5 (valores de 16 bits) caracteres. Esto permite que los valores impresos puedan sobreescribir en pantalla valores anteriores aunque estemos imprimiendo un valor "menor" que el que reside en pantalla:
  
 <code z80> <code z80>
Línea 700: Línea 699:
  De esta forma, podemos tener en pantalla un valor "12345" que se vea sobreescrito por un valor "100" al imprimir "  100" (con 2 espacios delante).  De esta forma, podemos tener en pantalla un valor "12345" que se vea sobreescrito por un valor "100" al imprimir "  100" (con 2 espacios delante).
  
- También podemos sustituir "CALL Font_Blankpor un simple "CALL Font_Inc_Xpara que se realice el avance del cursor horizontalmente pero sin la impresión del carácter espacio.+ También podemos sustituir ''CALL Font_Blank'' por un simple ''CALL Font_Inc_X'' para que se realice el avance del cursor horizontalmente pero sin la impresión del carácter espacio.
  
 \\ \\
Línea 760: Línea 759:
 ===== Fuente estándar 8x8 de la ROM ===== ===== Fuente estándar 8x8 de la ROM =====
  
- En el Spectrum disponemos de un tipo de letra estándar de 8x8 pregrabado en ROM. Los caracteres imprimibles alojados en la ROM del Spectrum van desde el 32 (espacio) al 127 (carácter de copyright), empezando el primero en $3D00 (15161 decimal) y acabando el último en $3FFF (16383, el último byte de la ROM).+ En el Spectrum disponemos de un tipo de letra estándar de 8x8 pregrabado en ROM. Los caracteres imprimibles alojados en la ROM del Spectrum van desde el 32 (espacio) al 127 (carácter de copyright), empezando el primero en **$3D00** (15161 decimal) y acabando el último en **$3FFF** (16383, el último byte de la ROM).
  
- Existe una variable del sistema llamada //CHARS// (de 16 bits, ubicada en las direcciones 23606 y 23607) que contiene la dirección de memoria del juego de caracteres que esté en uso por BASIC.+ Existe una variable del sistema llamada ''CHARS'' (de 16 bits, ubicada en las direcciones 23606 y 23607) que contiene la dirección de memoria del juego de caracteres que esté en uso por BASIC.
  
- Por defecto, CHARS contiene el valor $3D00 (el tipo de letra estándar) menos 256. El hecho de restar 256 al inicio real de la fuente es porque los caracteres definidos en la ROM empiezan en el 32 y restando 256 (8 bytes por carácter para 32 caracteres = 256 bytes), al igual que hicimos nosotros con nuestro charset personalizado, podemos hacer coincidir un ASCII > 32 con **CHARS+(8*VALOR_ASCII)**.+ Por defecto, ''CHARS'' contiene el valor **$3D00** (la fuente de letras estándar) menos 256. El hecho de restar 256 al inicio real de la fuente es porque los caracteres definidos en la ROM empiezan en el 32 y restando 256 (8 bytes por carácter para 32 caracteres = 256 bytes), al igual que hicimos nosotros con nuestro charset personalizado, podemos hacer coincidir un ASCII > 32 con **CHARS + (8 * VALOR_ASCII)**.
  
- El valor por defecto de //CHARS// es, pues, $3D00 - $0100 = $3C00.+ El valor por defecto de ''CHARS'' es, pues, $3D00 - $0100 = $3C00.
  
- El juego de caracteres estándar es inmutable al estar en ROM. La variable CHARS permitía, en el BASIC del Spectrum, definir un juego de caracteres personalizado en RAM y apuntar CHARS a su dirección en memoria. La definición de los 21 UDGs (19 en el +2A/+3) también está en RAM (desde $FF58 a $FFFF), ya que deben de ser personalizables por el usuario.+ El juego de caracteres estándar es inmutable al estar en ROM. La variable ''CHARS'' permite, en el BASIC del Spectrum, definir un juego de caracteres personalizado en RAM y apuntar ''CHARS'' a su dirección en memoria. La definición de los 21 UDGs (19 en el +2A/+3) también está en RAM (desde $FF58 a $FFFF), ya que deben de ser personalizables por el usuario.
  
  Veamos el aspecto de la tipográfia estándar del Spectrum:  Veamos el aspecto de la tipográfia estándar del Spectrum:
Línea 781: Línea 780:
  El formato en memoria de la fuente de la ROM es idéntico a un spriteset de 8x8 sin atributos, tal y como hemos definido las fuentes de texto personalizadas de nuestras rutinas y ejemplos anteriores. A partir de $3D00 empiezan los 8 bytes de datos (8 scanlines) del carácter 32, a los que siguen los 8 scanlines del carácter 33, etc., así hasta el carácter 127.  El formato en memoria de la fuente de la ROM es idéntico a un spriteset de 8x8 sin atributos, tal y como hemos definido las fuentes de texto personalizadas de nuestras rutinas y ejemplos anteriores. A partir de $3D00 empiezan los 8 bytes de datos (8 scanlines) del carácter 32, a los que siguen los 8 scanlines del carácter 33, etc., así hasta el carácter 127.
  
- Gracias a esto podemos utilizar esta tipografía en nuestros juegos y programas (ahorrando así tener que definir nuestro propio charset y ocupar memoria con él) directamente con las rutinas de impresión de caracteres y cadenas que hemos utilizado con las fuentes de texto personalizables. Basta con establecer FONT_CHARSET a la dirección adecuada, $3C00:+ Gracias a esto podemos utilizar esta tipografía en nuestros juegos y programas (ahorrando así tener que definir nuestro propio charset y ocupar memoria con él) directamente con las rutinas de impresión de caracteres y cadenas que hemos utilizado con las fuentes de texto personalizables. Basta con establecer ''FONT_CHARSET'' a la dirección adecuada, $3C00:
  
 <code z80> <code z80>
Línea 799: Línea 798:
  Normalmente no se usará este tipo de rutinas en un juego arcade o videoaventura, pero puede aprovecharse en programas no lúdicos y en juegos basados en texto o con gran cantidad de texto (managers deportivos, aventuras de texto, RPGs, etc).  Normalmente no se usará este tipo de rutinas en un juego arcade o videoaventura, pero puede aprovecharse en programas no lúdicos y en juegos basados en texto o con gran cantidad de texto (managers deportivos, aventuras de texto, RPGs, etc).
  
- Ya hemos visto cómo las rutinas PrintChar_8x8 y PrintString_8x8 hacen uso de las variables FONT_X, FONT_Y, FONT_CHARSET y FONT_ATTRIB. En este apartado definiremos más variables, funciones para manipularlas y nuevas funciones de impresión que hagan uso avanzado de ambas.+ Ya hemos visto cómo las rutinas ''PrintChar_8x8'' ''PrintString_8x8'' hacen uso de las variables ''FONT_X''''FONT_Y''''FONT_CHARSET'' ''FONT_ATTRIB''. En este apartado definiremos más variables, funciones para manipularlas y nuevas funciones de impresión que hagan uso avanzado de ambas.
  
  La sección sobre sistemas de gestión de texto se divide en:  La sección sobre sistemas de gestión de texto se divide en:
Línea 1107: Línea 1106:
 </code> </code>
  
- Nótese que hemos creado funciones del tipo Font_Set_X o Font_Set_Y que simplemente modifican valores en nuestras variables por temas ilustrativos, ya que lo normal sería en nuestro ejemplo escribir directamente en las variables en lugar de hacer un CALL.+ Nótese que hemos creado funciones del tipo ''Font_Set_X'' ''Font_Set_Y'' que simplemente modifican valores en nuestras variables por temas ilustrativos, ya que lo normal sería en nuestro ejemplo escribir directamente en las variables en lugar de hacer un ''CALL''.
  
  
Línea 1114: Línea 1113:
 ==== Impresión de caracteres con estilos ==== ==== Impresión de caracteres con estilos ====
  
- Aunque ya hemos visto una rutina PrintChar_8x8 para impresión de caracteres, vamos a implementar a continuación una nueva versión de la misma con la posibilidad de utilizar diferente estilos de fuente a partir de la fuente original.+ Aunque ya hemos visto una rutina ''PrintChar_8x8'' para impresión de caracteres, vamos a implementar a continuación una nueva versión de la misma con la posibilidad de utilizar diferente estilos de fuente a partir de la fuente original.
  
  Mediante un único juego de caracteres podemos simular estilos de texto a través de código, manipulando "al vuelo" los datos del charset antes de imprimirlos. Esto nos evita la necesidad de tener múltiples charsets de texto para distintos estilos con la consiguiente ocupación de espacio en memoria.  Mediante un único juego de caracteres podemos simular estilos de texto a través de código, manipulando "al vuelo" los datos del charset antes de imprimirlos. Esto nos evita la necesidad de tener múltiples charsets de texto para distintos estilos con la consiguiente ocupación de espacio en memoria.
Línea 1120: Línea 1119:
  Los estilos básicos que podemos conseguir al vuelo son **normal**, **negrita**, **cursiva** y **subrayado**.  Los estilos básicos que podemos conseguir al vuelo son **normal**, **negrita**, **cursiva** y **subrayado**.
  
- Las rutinas de impresión para los 4 estilos esencialmente iguales salvo por el bucle de impresión, por lo que vamos a utilizar una variable global llamada **FONT_STYLE** para indicar el estilo actual en uso, y modificaremos la rutina PrintChar_8x8 para que haga uso del valor del estilo y modifique el bucle de impresión en consecuencia.+ Las rutinas de impresión para los 4 estilos esencialmente iguales salvo por el bucle de impresión, por lo que vamos a utilizar una variable global llamada ''FONT_STYLE'' para indicar el estilo actual en uso, y modificaremos la rutina ''PrintChar_8x8'' para que haga uso del valor del estilo y modifique el bucle de impresión en consecuencia.
  
 <code> <code>
Línea 1478: Línea 1477:
 </code> </code>
  
- Si definimos esta función PrintChar_8x8 en nuestro programa, la función PrintString_8x8 hará uso de ella y podremos imprimir cadenas en diferentes estilos, como en el siguiente ejemplo:+ Si definimos esta función ''PrintChar_8x8'' en nuestro programa, la función ''PrintString_8x8'' hará uso de ella y podremos imprimir cadenas en diferentes estilos, como en el siguiente ejemplo:
  
 <code z80> <code z80>
 ; Ejemplo de estilos de fuente ; Ejemplo de estilos de fuente
-ORG 35000+    ORG 35000
  
     LD HL, $3D00-256        ; Saltamos los 32 caracteres iniciales     LD HL, $3D00-256        ; Saltamos los 32 caracteres iniciales
Línea 1552: Línea 1551:
 FONT_SCRHEIGHT   EQU   24 FONT_SCRHEIGHT   EQU   24
  
-END 35000+    END 35000
 </code> </code>
  
Línea 1561: Línea 1560:
 \\ \\
  
- La rutina de impresión PrintChar_8x8 es ahora ligeramente más lenta que la original, pero a cambio nos permite diferentes estilos de texto. Para la impresión de texto con estilo normal, sólo le hemos añadido el siguiente código adicional a ejecutar:+ La rutina de impresión ''PrintChar_8x8'' es ahora ligeramente más lenta que la original, pero a cambio nos permite diferentes estilos de texto. Para la impresión de texto con estilo normal, sólo le hemos añadido el siguiente código adicional a ejecutar:
  
 <code z80> <code z80>
Línea 1573: Línea 1572:
 </code> </code>
  
- Son 13 (LD) + 4 (OR) + 7 (JR NZ sin salto) + 12 (JR) = 36 t-estados adicionales por carácter en estilo normal a cambio de la posibilidad de disponer de 4 estilos de texto diferentes para cualquier charset, incluído el de la ROM.+ Son 13 (''LD'') + 4 (''OR'') + 7 (''JR NZ sin salto'') + 12 (''JR'') = 36 t-estados adicionales por carácter en estilo normal a cambio de la posibilidad de disponer de 4 estilos de texto diferentes para cualquier charset, incluído el de la ROM.
  
  En la rutina se han utilizado operaciones de transferencia LD para imprimir los caracteres, lo que implica que no se respeta el fondo sobre el que se imprime, y se ponen a cero en pantalla todos los píxeles a cero en el charset. Este suele ser el sistema de impresión habitual puesto que el texto, para hacerlo legible, suele imprimirse sobre áreas en blanco de la pantalla, y un caracter impreso sobre otro debe sobreescribir totalmente al primero.  En la rutina se han utilizado operaciones de transferencia LD para imprimir los caracteres, lo que implica que no se respeta el fondo sobre el que se imprime, y se ponen a cero en pantalla todos los píxeles a cero en el charset. Este suele ser el sistema de impresión habitual puesto que el texto, para hacerlo legible, suele imprimirse sobre áreas en blanco de la pantalla, y un caracter impreso sobre otro debe sobreescribir totalmente al primero.
  
- No obstante, la rutina PrintChar_8x8 puede ser modificada por el lector, para utilizar operaciones OR en la transferencia a pantalla y por tanto respetar el contenido de pantalla al imprimir el carácter.+ No obstante, la rutina ''PrintChar_8x8'' puede ser modificada por el lector, para utilizar operaciones ''OR'' en la transferencia a pantalla y por tanto respetar el contenido de pantalla al imprimir el carácter.
  
  
Línea 1591: Línea 1590:
 \\ \\
  
- El siguiente paso en la escala de la gestión del texto sería la **impresión de cadenas con formato** para que aproveche nuestras nuevas funciones extendidas. Para esto modificaremos la rutina PrinString_8x8 vista al principio del capítulo de forma que haga uso no sólo de FONT_X y FONT_Y sino también de funciones adicionales que especificaremos en la cadena mediante códigos de control o //tokens//.+ El siguiente paso en la escala de la gestión del texto sería la **impresión de cadenas con formato** para que aproveche nuestras nuevas funciones extendidas. Para esto modificaremos la rutina ''PrinString_8x8'' vista al principio del capítulo de forma que haga uso no sólo de ''FONT_X'' ''FONT_Y'' sino también de funciones adicionales que especificaremos en la cadena mediante códigos de control o //tokens//.
  
  Los códigos de control que vamos a definir y utilizar serán los siguientes:  Los códigos de control que vamos a definir y utilizar serán los siguientes:
Línea 1647: Línea 1646:
  La rutina de impresión de cadenas deberá interpretar cada byte de la misma determinando:  La rutina de impresión de cadenas deberá interpretar cada byte de la misma determinando:
  
-  * Si es un código ASCII >= 32 -> Imprimir el caracter en FONT_X, FONT_Y (y variar las coordenadas). +  * Si es un código ASCII >= 32 -> Imprimir el caracter en ''FONT_X''''FONT_Y'' (y variar las coordenadas). 
-  * Si es un código de control 0 (EOS) -> Fin de la rutina. Nótese que, al contrario que en caso de la rutina PrintString que nos fabricamos para la ROM en nuestra librería **utils.asm**, en esta rutina podemos leer los parámetros de FLASH/BRIGHT e interpretarlos nosotros, por lo que un 0 que vaya detrás de un código de control, no se contabilizará como END_OF_STRING.+  * Si es un código de control 0 (''EOS'') -> Fin de la rutina. Nótese que, al contrario que en caso de la rutina PrintString que nos fabricamos para la ROM en nuestra librería **utils.asm**, en esta rutina podemos leer los parámetros de FLASH/BRIGHT e interpretarlos nosotros, por lo que un 0 que vaya detrás de un código de control, no se contabilizará como END_OF_STRING.
   * Si es un código entre el 1 y el 9 -> Recoger parámetro en cadena (siguiente byte) y llamar a la función apropiada.   * Si es un código entre el 1 y el 9 -> Recoger parámetro en cadena (siguiente byte) y llamar a la función apropiada.
   * Si es un código entre el 10 y el 31 -> Llamar a la función apropiada (no hay parámetro).   * Si es un código entre el 10 y el 31 -> Llamar a la función apropiada (no hay parámetro).
Línea 1654: Línea 1653:
  Esto nos permitirá trabajar con cadenas de texto con múltiples formatos sin tener que realizar el posicionamiento, cambio de color, de papel, gestión de los retornos de carro, etc. en nuestro código, con un gran ahorro en código de manipulación gracias a nuestra nueva rutina genérica de impresión.  Esto nos permitirá trabajar con cadenas de texto con múltiples formatos sin tener que realizar el posicionamiento, cambio de color, de papel, gestión de los retornos de carro, etc. en nuestro código, con un gran ahorro en código de manipulación gracias a nuestra nueva rutina genérica de impresión.
  
- Llamaremos a esta rutina de impresión con formato //PrintString_8x8_Format//, y tendrá el siguiente pseudocódigo:+ Llamaremos a esta rutina de impresión con formato ''PrintString_8x8_Format'', y tendrá el siguiente pseudocódigo:
  
 <code> <code>
Línea 1708: Línea 1707:
 PrintString_8x8_Format: PrintString_8x8_Format:
 bucle: bucle:
-      ;;; Coger caracter apuntador por HL.+      ;;; Coger caracter apuntado por HL.
       ;;; Incrementar HL       ;;; Incrementar HL
       ;;; Si HL es mayor que 32 :       ;;; Si HL es mayor que 32 :
Línea 1814: Línea 1813:
  
  
- El esqueleto de la rutina y la parte de impresión ya la conocemos, porque es idéntica a PrintString_8x8. El principal añadido es la interpretación de los códigos de control, donde la parte más interesante es la construcción y uso de la tabla de saltos:+ El esqueleto de la rutina y la parte de impresión ya la conocemos, porque es idéntica a ''PrintString_8x8''. El principal añadido es la interpretación de los códigos de control, donde la parte más interesante es la construcción y uso de la tabla de saltos:
  
- Una vez ubicadas todas las diferentes direcciones de las rutinas en FONT_CALL_JUMP_TABLE, podemos utilizar el valor del registro A para direccionar la tabla. Para ello debemos multiplicar A por 2 ya que cada dirección consta de 2 bytes. Cargando A*2 en BC podemos calcular la dirección destino en la tabla como HL+BC (BASE+DESPLAZAMIENTO = BASE+COD_CONTROL*2). Leyendo el valor apuntado por HL obtenemos la dirección de la tabla, es decir, la dirección de la rutina que puede interpretar el código de control que hemos recibido.+ Una vez ubicadas todas las diferentes direcciones de las rutinas en ''FONT_CALL_JUMP_TABLE'', podemos utilizar el valor del registro A para direccionar la tabla. Para ello debemos multiplicar A por 2 ya que cada dirección consta de 2 bytes. Cargando A*2 en BC podemos calcular la dirección destino en la tabla como HL+BC (BASE+DESPLAZAMIENTO = BASE+COD_CONTROL*2). Leyendo el valor apuntado por HL obtenemos la dirección de la tabla, es decir, la dirección de la rutina que puede interpretar el código de control que hemos recibido.
  
 <code z80> <code z80>
Línea 1843: Línea 1842:
 </code> </code>
  
- Con el anterior cálculo, por ejemplo, si recibimos un código de control 6, se pondrá en HL la dirección de memoria contenida en FONT_CALL_JUMP_TABLE+(6*2), que es el valor //Font_Set_Attrib//, que el ensamblador sustituirá en la tabla durante el proceso de ensamblado por la dirección de memoria de dicha rutina.+ Con el anterior cálculo, por ejemplo, si recibimos un código de control 6, se pondrá en HL la dirección de memoria contenida en FONT_CALL_JUMP_TABLE+(6*2), que es el valor ''Font_Set_Attrib'', que el ensamblador sustituirá en la tabla durante el proceso de ensamblado por la dirección de memoria de dicha rutina.
  
- Nótese cómo después de calcular el valor de salto correcto para HL tenemos que simular un "CALL HL", que no forma parte del juego de instrucciones del Spectrum. ¿Cómo realizamos esto? Utilizando la pila y la instrucción JP. Recordemos que un CALL es un salto a subrutina, lo cual implica introducir en la pila la dirección de retorno y salta a la dirección de la rutina. Cuando la rutina realice el RET, se extrae de la pila la dirección de retorno para continuar el flujo del programa.+ Nótese cómo después de calcular el valor de salto correcto para HL tenemos que simular un ''CALL HL'' que no forma parte del juego de instrucciones del Spectrum. ¿Cómo realizamos esto? Utilizando la pila y la instrucción JP. Recordemos que un CALL es un salto a subrutina, lo cual implica introducir en la pila la dirección de retorno y salta a la dirección de la rutina. Cuando la rutina realice el RET, se extrae de la pila la dirección de retorno para continuar el flujo del programa.
  
- En el código anterior introducimos en el registro BC la dirección de la etiqueta **pstring8_retaddr**, que es la posición exacta de memoria después del salto. Una vez introducida en la pila la dirección de retorno, saltamos con el salto incondicional **JP (HL)** a la rutina especificada por el código de control. La subrutina efectuará la tarea correspondiente y volverá con un RET, provocando que la rutina de impresión de cadenas continúe en pstring8_retaddr, que es la dirección que el RET extraerá de la pila para volver.+ En el código anterior introducimos en el registro BC la dirección de la etiqueta ''pstring8_retaddr'', que es la posición exacta de memoria después del salto. Una vez introducida en la pila la dirección de retorno, saltamos con el salto incondicional ''JP (HL)'' a la rutina especificada por el código de control. La subrutina efectuará la tarea correspondiente y volverá con un RET, provocando que la rutina de impresión de cadenas continúe en ''pstring8_retaddr'', que es la dirección que el ''RET'' extraerá de la pila para volver.
  
  Hemos hecho distinción de 2 tipos de subrutinas de control, ya que las 9 primeras requieren recoger un parámetro de la cadena (apuntado por HL) y las restantes no. El cálculo de la dirección de salto es igual en todos los casos pero para las 9 primeras es necesario obtener el dato adicional al código de control en el registro A antes de saltar. El registro A es el parámetro común en todas las subrutinas de gestión de códigos de control que requieren parámetros, algo necesario para poder usar las rutinas vía tabla de saltos.  Hemos hecho distinción de 2 tipos de subrutinas de control, ya que las 9 primeras requieren recoger un parámetro de la cadena (apuntado por HL) y las restantes no. El cálculo de la dirección de salto es igual en todos los casos pero para las 9 primeras es necesario obtener el dato adicional al código de control en el registro A antes de saltar. El registro A es el parámetro común en todas las subrutinas de gestión de códigos de control que requieren parámetros, algo necesario para poder usar las rutinas vía tabla de saltos.
Línea 1862: Línea 1861:
  En lugar de volver a dividir el código de control entre 2 (recordemos que se multiplicó por 2 para el cálculo de la dirección de salto) y comprobar si es > 9, podemos comprobar directamente si es > 9*2 = 18.  En lugar de volver a dividir el código de control entre 2 (recordemos que se multiplicó por 2 para el cálculo de la dirección de salto) y comprobar si es > 9, podemos comprobar directamente si es > 9*2 = 18.
  
- Tras interpretar el código de control, bastará con volver a saltar al principio de la rutina para continuar con el siguiente carácter. Todo el proceso se repetirá hasta recibir en A un código de control 0 (FONT_EOS, de FONT_END_OF_STRING).+ Tras interpretar el código de control, bastará con volver a saltar al principio de la rutina para continuar con el siguiente carácter. Todo el proceso se repetirá hasta recibir en A un código de control 0 (''FONT_EOS'', de FONT_END_OF_STRING).
  
  Una vez explicada la rutina, veamos un ejemplo de cómo podríamos utilizarla en nuestros programas:  Una vez explicada la rutina, veamos un ejemplo de cómo podríamos utilizarla en nuestros programas:
Línea 1868: Línea 1867:
 <code z80> <code z80>
 ; Ejemplo de gestion de texto ; Ejemplo de gestion de texto
-ORG 35000+    ORG 35000
  
     LD HL, $3D00-256        ; Saltamos los 32 caracteres iniciales     LD HL, $3D00-256        ; Saltamos los 32 caracteres iniciales
Línea 1898: Línea 1897:
         DB FONT_EOS         DB FONT_EOS
  
-END 35000+    END 35000
 </code> </code>
  
Línea 1913: Línea 1912:
  Recomendamos al lector que utilice siempre en sus cadenas los códigos de control mediante las constantes EQU en lugar de utilizar los códigos numéricos en sí mismos. Esto permite reubicar los valores numéricos (los EQUs) sin modificar las cadenas. Recordemos que el ensamblador sustituirá las constantes por sus valores numéricos durante el proceso de ensamblado, por lo que la ocupación en las cadenas definitivas no será "mayor" al usar las constantes. El único código de control que no debe reubicarse nunca es FONT_EOS (0).  Recomendamos al lector que utilice siempre en sus cadenas los códigos de control mediante las constantes EQU en lugar de utilizar los códigos numéricos en sí mismos. Esto permite reubicar los valores numéricos (los EQUs) sin modificar las cadenas. Recordemos que el ensamblador sustituirá las constantes por sus valores numéricos durante el proceso de ensamblado, por lo que la ocupación en las cadenas definitivas no será "mayor" al usar las constantes. El único código de control que no debe reubicarse nunca es FONT_EOS (0).
  
- Finalmente, creemos importante indicar al lector que para marcar claramente la dirección de salto del código de control 9 (que no está en uso) se ha usado la cadena "0000", pero probablemente sería más seguro el colocar la dirección de una rutina como FONT_TAB o FONT_CRLF. De esta forma, ante un error del programador al escribir una cadena y utilizar el inexistente código 9 en ella, evitaremos que se produzca un reset (JP $0000) que nos cueste gran cantidad de horas de encontrar / depurar.+ Finalmente, creemos importante indicar al lector que para marcar claramente la dirección de salto del código de control 9 (que no está en uso) se ha usado la cadena "0000", pero probablemente sería más seguro el colocar la dirección de una rutina como FONT_TAB o FONT_CRLF. De esta forma, ante un error del programador al escribir una cadena y utilizar el inexistente código 9 en ella, evitaremos que se produzca un reset (''JP $0000'') que nos cueste gran cantidad de horas de encontrar / depurar.
  
  En cuanto a las diferencias en tiempo de ejecución de PrintString_8x8_Format vs PrintString_8x8, cabe destacar que el coste adicional de la rutina para la impresión del texto normal (ASCIIs < 32) se reduce a las siguientes 2 instrucciones adicionales:  En cuanto a las diferencias en tiempo de ejecución de PrintString_8x8_Format vs PrintString_8x8, cabe destacar que el coste adicional de la rutina para la impresión del texto normal (ASCIIs < 32) se reduce a las siguientes 2 instrucciones adicionales:
Línea 1922: Línea 1921:
 </code> </code>
  
- Aparte de eso, se ha sustituído el código de avance de la coordenada X por el de las rutinas genéricas vistas anteriormente, lo que añade un //CALL Font_Inc_X// (y su RET) adicional. Así pues, el coste en tiempo de ejecución no difiere apenas de la función sin códigos de control.+ Aparte de eso, se ha sustituído el código de avance de la coordenada X por el de las rutinas genéricas vistas anteriormente, lo que añade un ''CALL Font_Inc_X'' (y su ''RET'') adicional. Así pues, el coste en tiempo de ejecución no difiere apenas de la función sin códigos de control.
  
- En el caso del código de fin de cadena (EOS = 0), ya no se sale de la rutina con un RET Z sino que se pasa por el **CP 32** y se realiza el salto a //pstring8_ccontrol//.+ En el caso del código de fin de cadena (EOS = 0), ya no se sale de la rutina con un RET Z sino que se pasa por el ''CP 32'' y se realiza el salto a ''pstring8_ccontrol''.
  
- Sí que hay un coste real en la ocupación de memoria, puesto que todas las funciones auxiliares de control que hemos definido seguramente pueden no resultarnos útiles en la programación de un juego donde no se utilice apenas texto. Ese código adicional sumado a la gestión de códigos de control de la rutina y a la tabla de saltos puede ser espacio utilizable por nosotros si empleados la rutina sin formato //PrintString_8x8//.+ Sí que hay un coste real en la ocupación de memoria, puesto que todas las funciones auxiliares de control que hemos definido seguramente pueden no resultarnos útiles en la programación de un juego donde no se utilice apenas texto. Ese código adicional sumado a la gestión de códigos de control de la rutina y a la tabla de saltos puede ser espacio utilizable por nosotros si empleados la rutina sin formato ''PrintString_8x8''.
  
  Donde no hay duda de la gran utilidad de las anteriores rutinas es en cualquier juego basado en texto, donde nos evitamos realizar el formato de los textos en base a programación y llamadas continuadas a las funciones de formato, posicionamiento, etc. Bastará con definir las cadenas en nuestro programa con el formato adecuado. El ahorro en líneas de código será muy considerable.  Donde no hay duda de la gran utilidad de las anteriores rutinas es en cualquier juego basado en texto, donde nos evitamos realizar el formato de los textos en base a programación y llamadas continuadas a las funciones de formato, posicionamiento, etc. Bastará con definir las cadenas en nuestro programa con el formato adecuado. El ahorro en líneas de código será muy considerable.
Línea 1933: Línea 1932:
 ==== Impresión avanzada: datos variables ==== ==== Impresión avanzada: datos variables ====
  
- Nuestro siguiente objetivo es extender PringString_8x8_Format para permitir la utilización de códigos de control que representen valores de variables. El objetivo es simular la funcionalidad de la función printf() del lenguaje C, el cual permite impresiones de cadena como la siguiente:+ Nuestro siguiente objetivo es extender ''PringString_8x8_Format'' para permitir la utilización de códigos de control que representen valores de variables. El objetivo es simular la funcionalidad de la función printf() del lenguaje C, el cual permite impresiones de cadena como la siguiente:
  
 <code c> <code c>
Línea 1939: Línea 1938:
 </code> </code>
  
- Para eso vamos a crear una nueva rutina **PrintString_8x8_Format_Args** que además de los códigos de control de formato, comprenda códigos para la impresión de variables de cadena o numéricas en representación decimal, hexadecimal o binaria.+ Para eso vamos a crear una nueva rutina ''PrintString_8x8_Format_Args'' que además de los códigos de control de formato, comprenda códigos para la impresión de variables de cadena o numéricas en representación decimal, hexadecimal o binaria.
  
  Los nuevos códigos de control imitarán el formato de C (símbolo de % seguido de un identificador del tipo de variable) y podrán estar así integrados dentro del propio texto:  Los nuevos códigos de control imitarán el formato de C (símbolo de % seguido de un identificador del tipo de variable) y podrán estar así integrados dentro del propio texto:
Línea 1969: Línea 1968:
  Nótese que podríamos haber empleado el sistema de códigos de formato con los ASCIIs libres entre el 17 y el 31. El lector puede adaptar fácilmente la rutina a ese sistema si así lo deseara.  Nótese que podríamos haber empleado el sistema de códigos de formato con los ASCIIs libres entre el 17 y el 31. El lector puede adaptar fácilmente la rutina a ese sistema si así lo deseara.
  
- Volvamos a PrintString_8x8_Format_Args: Nuestra nueva rutina deberá recibir ahora un parámetro adicional: además de la cadena a imprimir en HL, deberemos apuntar el registro IX a un array con los datos a sustuitir, o apuntando a una única variable de memoria si sólo hay un parámetro.+ Volvamos a ''PrintString_8x8_Format_Args'': Nuestra nueva rutina deberá recibir ahora un parámetro adicional: además de la cadena a imprimir en HL, deberemos apuntar el registro IX a un array con los datos a sustuitir, o apuntando a una única variable de memoria si sólo hay un parámetro.
  
- La rutina es similar a PrintString_8x8_Format, pero añadiendo lo siguiente:+ La rutina es similar a ''PrintString_8x8_Format'', pero añadiendo lo siguiente:
  
 <code> <code>
Línea 2282: Línea 2281:
 <code z80> <code z80>
 ; Ejemplo de impresion de texto con argumentos ; Ejemplo de impresion de texto con argumentos
-ORG 35000+    ORG 35000
  
     LD HL, $3D00-256     LD HL, $3D00-256
Línea 2322: Línea 2321:
 args2   DB 2, "cad 1", FONT_EOS, "cad 2", FONT_EOS args2   DB 2, "cad 1", FONT_EOS, "cad 2", FONT_EOS
  
-END 35000+    END 35000
 </code> </code>
  
Línea 2342: Línea 2341:
 </code> </code>
  
- Sí que hay que ser especialmente cuidadoso a la hora de definir los parámetros en la variable que apuntamos con IX: es importante que cada parámetro tenga su tamaño adecuado (DB, DW), y que no le falten los End Of String (0) a las cadenas.+ Sí que hay que ser especialmente cuidadoso a la hora de definir los parámetros en la variable que apuntamos con IX: es importante que cada parámetro tenga su tamaño adecuado (''DB''''DW''), y que no le falten los End Of String (0) a las cadenas.
  
  Nótese que los parámetros que se imprimen pueden ser modificados por el programa, por lo que esta rutina es muy útil en juegos o programas que trabajen con muchos datos a mostrar.  Nótese que los parámetros que se imprimen pueden ser modificados por el programa, por lo que esta rutina es muy útil en juegos o programas que trabajen con muchos datos a mostrar.
Línea 2365: Línea 2364:
 \\ \\
  
- A continuación realizamos las modificaciones adecuadas a la rutina PrintString_8x8_Format_Args:+ A continuación realizamos las modificaciones adecuadas a la rutina ''PrintString_8x8_Format_Args'':
  
 <code z80> <code z80>
Línea 2439: Línea 2438:
 </code> </code>
  
- Lo normal a la hora de utilizar PrintString_Format_Args en nuestro programa es que eliminemos todos aquellos códigos de control (y sus rutinas de chequeo y de gestión) de los cuales no vayamos a hacer uso, con el consiguiente ahorro de memoria (desaparecen las instrucciones de las subrutinas de gestión).+ Lo normal a la hora de utilizar ''PrintString_Format_Args'' en nuestro programa es que eliminemos todos aquellos códigos de control (y sus rutinas de chequeo y de gestión) de los cuales no vayamos a hacer uso, con el consiguiente ahorro de memoria (desaparecen las instrucciones de las subrutinas de gestión).
  
 \\ \\
Línea 2451: Línea 2450:
  En el artículo dedicado al teclado estudiamos rutinas de lectura del mismo que nos proporcionaban scancodes de las teclas pulsadas. También vimos rutinas de obtención del código ASCII correspondiente a un scancode dado.  En el artículo dedicado al teclado estudiamos rutinas de lectura del mismo que nos proporcionaban scancodes de las teclas pulsadas. También vimos rutinas de obtención del código ASCII correspondiente a un scancode dado.
  
- En este caso necesitaremos una rutina más "avanzada"que permita detectar el uso de CAPS SHIFT y DELETE (CAPS SHIFT + '0') y que distinga por tanto entre mayúsculas y minúsculas. Para eso, utilizaremos la rutina de escaneo de teclado y conversión a ASCII de la ROM del Spectrum (**KEY_SCAN**), ubicada en la dirección de memoria $028E de la ROM.+ En este caso vamos a utilizar para nuestro ejemplo la rutina de la ROM ''KEY_SCAN''la cual permite detectar el uso de CAPS SHIFT y DELETE (CAPS SHIFT + '0') y que distingue por tanto entre mayúsculas y minúsculas. ''KEY_SCAN'' está ubicada en la dirección de memoria $028E de la ROM.
  
- Al realizar un CALL a KEY_SCAN se produce una lectura de todas las filas del teclado seguida de una decodificación del resultado de la lectura. La rutina de la ROM coloca entonces en la variable del sistema **LAST_K** (dirección 23560) el código ASCII de la tecla pulsada. KEY_SCAN también decodifica las teclas extendidas y LAST_K nos puede servir también para detectar ENTER (código ASCII 13) o DELETE (código ASCII 12).+ Al realizar un CALL a ''KEY_SCAN'' se produce una lectura de todas las filas del teclado seguida de una decodificación del resultado de la lectura. La rutina de la ROM coloca entonces en la variable del sistema ''LAST_K'' (dirección 23560) el código ASCII de la tecla pulsada. ''KEY_SCAN'' también decodifica las teclas extendidas y ''LAST_K'' nos puede servir también para detectar ENTER (código ASCII 13) o DELETE (código ASCII 12).
  
  El desarrollo de la rutina será el siguiente:  El desarrollo de la rutina será el siguiente:
Línea 2482: Línea 2481:
 </code> </code>
  
- Veamos el código de la rutina **InputString_8x8**:+ Veamos el código de la rutina ''InputString_8x8'':
  
 <code z80> <code z80>
Línea 2578: Línea 2577:
 </code> </code>
  
- InputString_8x8 utiliza una nueva subrutina llamada **Font_SafePrintChar8x8** que no es más que una encapsulación del PrintChar_8x8 original en la que se preservan y restauran los valores de los registros que modifica internamente PrintChar:+'' InputString_8x8'' utiliza una nueva subrutina llamada ''Font_SafePrintChar8x8'' que no es más que una encapsulación del ''PrintChar_8x8'' original en la que se preservan y restauran los valores de los registros que modifica internamente ''PrintChar'':
  
 <code z80> <code z80>
Línea 2599: Línea 2598:
 <code z80> <code z80>
 ; Ejemplo de input de texto ; Ejemplo de input de texto
-ORG 35000+    ORG 35000
  
     LD HL, $3D00-256     LD HL, $3D00-256
Línea 2636: Línea 2635:
         DB 0         DB 0
  
-END 35000+    END 35000
 </code> </code>
  
Línea 2646: Línea 2645:
  
 \\ \\
-  * Permitir edición multilínea. La rutina actual no permite trabajar (al menos en cuanto al borrado) con entrada de texto de múltiples líneas. Se podría editar la rutina para permitir editar más de una línea de texto, realizando una versión especial de Font_Dec_X que decremente el valor de FONT_Y y ponga FONT_X=0 cuando tratemos de borrar desde el margen izquierdo de la pantalla. +  * Permitir edición multilínea. La rutina actual no permite trabajar (al menos en cuanto al borrado) con entrada de texto de múltiples líneas. Se podría editar la rutina para permitir editar más de una línea de texto, realizando una versión especial de ''Font_Dec_X'' que decremente el valor de ''FONT_Y'' y ponga ''FONT_X=0'' cuando tratemos de borrar desde el margen izquierdo de la pantalla. 
-  * Habilitar el uso de las teclas de cursor para moverse entre los caracteres de la cadena y así permitir edición avanzada. La rutina debería basarse entonces en un FONT_X y FONT_Y propios y ya no se podría utilizar FONT_BACKSPACE para el borrado. Además, al insertar un carácter en el interior de la cadena habría que mover todos los caracteres en memoria una posición a la derecha y redibujar la cadena completa en pantalla. El cursor podría simularse entonces con FLASH o subrayando la letra actual (por lo que no serviría para editar texto subrayado).+  * Habilitar el uso de las teclas de cursor para moverse entre los caracteres de la cadena y así permitir edición avanzada. La rutina debería basarse entonces en un ''FONT_X'' ''FONT_Y'' propios y ya no se podría utilizar ''FONT_BACKSPACE'' para el borrado. Además, al insertar un carácter en el interior de la cadena habría que mover todos los caracteres en memoria una posición a la derecha y redibujar la cadena completa en pantalla. El cursor podría simularse entonces con FLASH o subrayando la letra actual (por lo que no serviría para editar texto subrayado).
   * Permitir llamar a la función con una cadena ya en la zona apuntada por HL. En conjunción con la mejora anterior permitiría editar texto anteriormente introducido.   * Permitir llamar a la función con una cadena ya en la zona apuntada por HL. En conjunción con la mejora anterior permitiría editar texto anteriormente introducido.
 \\ \\
Línea 2669: Línea 2668:
 \\ \\
  
- Para utilizar este set de caracteres sólo tendremos que realizar una nueva rutina de impresión llamada **PrintChar_4x8** y modificar la variable que define la anchura de la pantalla, FONT_SWIDTH (que pasa de valer 32 a 64).+ Para utilizar este set de caracteres sólo tendremos que realizar una nueva rutina de impresión llamada ''PrintChar_4x8'' y modificar la variable que define la anchura de la pantalla, ''FONT_SWIDTH'' (que pasa de valer 32 a 64).
  
  La definición de la fuente de 4x8 píxeles en formato DB es la siguiente:  La definición de la fuente de 4x8 píxeles en formato DB es la siguiente:
Línea 2727: Línea 2726:
  Al trazar el carácter en pantalla tenemos que hacerlo con OR para respetar otro posible carácter de 4x8 que pueda haber en el mismo bloque, ya lo estemos imprimiendo en la parte izquierda de un bloque (respetar el nibble de la derecha) o en la derecha (respetar el nibble de la izquierda).  Al trazar el carácter en pantalla tenemos que hacerlo con OR para respetar otro posible carácter de 4x8 que pueda haber en el mismo bloque, ya lo estemos imprimiendo en la parte izquierda de un bloque (respetar el nibble de la derecha) o en la derecha (respetar el nibble de la izquierda).
  
- Veamos la rutina de impresión **PrintChar_4x8** seguida de la 4 subrutinas de volcado de carácter que son llamados una vez calculados HL y DE como origen y destino.+ Veamos la rutina de impresión ''PrintChar_4x8'' seguida de la 4 subrutinas de volcado de carácter que son llamados una vez calculados HL y DE como origen y destino.
  
 <code z80> <code z80>
Línea 2903: Línea 2902:
 </code> </code>
  
- También habría que crear un PrintString_4x8 idéntico a PrintString_8x8 pero que llame a PrintChar_4x8, y modificar aquellas rutinas que hagan referencia a una de estas 2 funciones, como por ejemplo:+ También habría que crear un ''PrintString_4x8'' idéntico a ''PrintString_8x8'' pero que llame a ''PrintChar_4x8'', y modificar aquellas rutinas que hagan referencia a una de estas 2 funciones, como por ejemplo:
  
 <code z80> <code z80>
Línea 2923: Línea 2922:
 <code z80> <code z80>
 ; Ejemplo de fuente de 4x8 pixeles (64 caracteres por linea) ; Ejemplo de fuente de 4x8 pixeles (64 caracteres por linea)
-ORG 35000+    ORG 35000
  
     LD HL, charset_4x8-128                    ; Inicio charset - (256/2)     LD HL, charset_4x8-128                    ; Inicio charset - (256/2)
Línea 2949: Línea 2948:
         DB FONT_EOS         DB FONT_EOS
  
-END 35000+    END 35000
 </code> </code>
  
Línea 2956: Línea 2955:
 \\ \\
  
- Nótese cómo hemos inicializado FONT_CHARSET a la dirección de la fuente menos 128 en lugar de restarle 256. Esto se debe a que la fuente tiene 2 caracteres definidos en cada byte y vamos a dividir el ASCII entre 2 en nuestra rutina, por lo que el carácter en que empieza nuestra fuente, el 32, está en charset4x8 - (256/2) = charset4x8 - 128.+ Nótese cómo hemos inicializado ''FONT_CHARSET'' a la dirección de la fuente menos 128 en lugar de restarle 256. Esto se debe a que la fuente tiene 2 caracteres definidos en cada byte y vamos a dividir el ASCII entre 2 en nuestra rutina, por lo que el carácter en que empieza nuestra fuente, el 32, está en charset4x8 - (256/2) = charset4x8 - 128.
  
  Otro detalle importante es el tema de los atributos: como cada bloque de pantalla contiene 2 caracteres, no podemos establecer atributos diferentes para 2 caracteres del mismo byte. Por esto, hay que ser cauto a la hora de establecer atributos. La solución más sencilla es cambiar las tintas en posiciones donde haya espacios, ya que en ese caso el cambio será efectivo en la letra deseada si ésta es la primera del byte, o en el espacio seguido de la letra deseada si está en la parte derecha. Los cambios de PAPER, BRIGHT o FLASH supondrán problemas si no se realizan siempre en posiciones de pantalla pares.  Otro detalle importante es el tema de los atributos: como cada bloque de pantalla contiene 2 caracteres, no podemos establecer atributos diferentes para 2 caracteres del mismo byte. Por esto, hay que ser cauto a la hora de establecer atributos. La solución más sencilla es cambiar las tintas en posiciones donde haya espacios, ya que en ese caso el cambio será efectivo en la letra deseada si ésta es la primera del byte, o en el espacio seguido de la letra deseada si está en la parte derecha. Los cambios de PAPER, BRIGHT o FLASH supondrán problemas si no se realizan siempre en posiciones de pantalla pares.
  • cursos/ensamblador/gfx4_fuentes.txt
  • Última modificación: 19-01-2024 08:23
  • por sromero