Juego de caracteres en Beta BASIC; problemas con causa
Hace un par de semanas escribí acerca de un problema para usar un juego de caracteres con Beta BASIC, que me encontré durante el desarrollo de Alien-B. Unos días después bastó un poco de lógica sosegada para encontrar la causa del problema y diseñar una solución.
El origen de todo era una confusión: lo que el manual de Beta BASIC dice acerca de CLEAR
es cierto, y los ejemplos en Beta BASIC Newsletter (número 2, página 3) son válidos; pero en ambos documentos se da por supuesto que previamente no ha sido definida ninguna ventana ni ningún atajo de teclado. Los datos de las ventanas y los atajos de teclado usan la zona entre el límite superior de la memoria utilizable por el programa y el inicio del código de Beta BASIC. Eso permite preservar esas zonas cuando borramos el programa, y también guardarlas junto con el código de Beta BASIC. Si no hay ventanas o atajos definidos, esa zona está vacía y sólo hay dos octetos de diferencia entre el límite superior de la memoria utilizable y el comienzo del código de Beta BASIC, como se muestra en los ejemplos de en Beta BASIC Newsletter. El problema es que cuando hay atajos de teclado o ventanas definidos y usamos CLEAR
con un valor menor de 768, no es posible saber dónde termina la zona de memoria que almacena ambas tablas de datos, y el espacio libre creado se encuentra entre ella y el código de Beta BASIC. La documentación menciona vagamente un cero usado como indicador de final de dicha zona, pero los exámenes de memoria que he realizado no me han permitido identificarlo con seguridad.
Por tanto, la solución que me quedaba era la más sencilla: reservar el espacio necesario para el nuevo juego de caracteres antes de definir ventanas o atajos de teclado.
Sin embargo se me planteó un problema nuevo: Al reiniciar el programa con RUN
, ¿cómo saber si el juego de caracteres ya había sido cargado en una ejecución anterior? Y, en dicho caso, ¿cómo saber la dirección de memoria en que se encuentra?
La solución a este nuevo problema la encontré en el utilísimo disco RAM de la ZX Spectrum 128, para cuyo aprovechamiento Beta BASIC proporciona buenas facilidades.
En primer lugar, necesitaba crear una constante con el nombre del fichero que, en el disco RAM, conservaría la dirección del juego de caracteres la primera vez que fuera cargado:
120
REM Constants
280 DIM f$(10)
LET f$="font#"
REM name of the RAM disc array that holds the address of the game font.
Después necesitaba crear una matriz numérica en el disco RAM con dicho nombre de fichero, pero sólo si no existiera ya:
300
REM Variables
320 IF NOT INSTRING(1,CAT$(),f$) THEN DIM !f$,(1)
REM storage for the address of the game font
A continuación había que reordenar las llamadas a los procedimientos de inicialización, para cargar el juego de caracteres antes de definir las ventanas y los atajos de teclado:
350
REM Setup
360 setupUdgs
370 setupGraphs
380 gameFont
REM This must be before setupScreen and any DEF KEY
390 setupScreen
400 DEF KEY "0"," INK 4: PAPER 0: BORDER 0: CSIZE 0: CLS :normalFont: KEYWORDS 1"
Por último, había que reescribir los procedimientos de carga del juego de caracteres para que comprobaran si la matriz en el disco RAM contenía un número distinto de cero, en cuyo caso debían activar el juego de caracteres presente en dicha dirección de memoria en lugar de cargarlo de nuevo desde el disquete:
2550
REM Setup
2560
REM Fonts
2570 DEF PROC gameFont
2580 LOCAL address
2590 LET address=!f$,(1)
2600 IF NOT address THEN allocate 768,address
LOAD d*"alien.font"CODE address
LET address=address-256
LET !f$,(1)=address
2610 DPOKE sysCHARS,address
2620 END PROC
2630 DEF PROC normalFont
2640 DPOKE sysCHARS,sysFont
2650 END PROC
2660 DEF PROC eraseFont
2670 superclear -768
normalFont
2680 END PROC
Y por supuesto, hacen falta los procedimientos para reservar cómodamente bloques de memoria mayores de 767 octetos:
2350
REM Memory management
2360 DEF PROC superclear bytes
2370 REM Do with any bytes amount what Beta BASIC's CLEAR does only with values under 768 bytes.
2380 LOCAL sign,n
2390 LET sign=SGN (bytes),bytes=ABS (bytes)
2400 FOR n=1 TO INT (bytes/767)
CLEAR 767*sign
NEXT n
CLEAR MOD(bytes,767)*sign
2410 END PROC
2420 DEF PROC allocate bytes, REF address
2430 REM Lower the RAMTOP to get the specified bytes of free space, and return its address
2440 REM In Beta BASIC Newsletter #2, page 3, there is an example how to calculate the address.
2450 LOCAL oldRamtop
2460 LET oldRamtop=DPEEK(sysRAMTOP),bytes=ABS (bytes)
2470 superclear bytes
2480 LET address=oldRamtop-bytes+2
2490 REM PRINT address,DPEEK(sysramtop)
rem debug!!!
2500 END PROC
2510 DEF PROC deallocate bytes
2520 REM Upper the RAMTOP to remove the specified bytes of free space.
2530 superclear -ABS (bytes)
2540 END PROC
Hace unos años escuché al bueno de Richard Stallman explicar, en una conferencia en Madrid, por qué la programación le parecía apasionante. No recuerdo sus palabras exactas pero sí su contenido: la programación es un gran juego que te permite hacer lo que quieras con tu intelecto y tu imaginación, sin límites. Así es. Yo añadiría además que la programación es muy gratificante porque nada en ella carece de causa lógica; incluso cuando un programa no funciona bien y tras muchas horas de trabajo no encuentro cuál es la causa del problema, sé que dicha causa existe y que antes o después la encontraré y, si no es externa, podré hacer los cambios necesarios y el problema desaparecerá.