DEV Community

¿Qué se puede hacer con un The Spectrum?

The Spectrum, de Retro Games

Hacía tiempo que tenía ganas de escribir otra entrada sobre el entrañable Spectrum, y The Spectrum, una máquina retro que se agotó al poco de salir, me ha dado la excusa perfecta.

La máquina viene con una ROM creada para la ocasión soportando 128k, con el editor del 48k. En realidad, puede emular casi cualquier modelo real de Spectrum, si le proporcionas las ROM's del Speccy original adecuadas. Basta formatear un pendrive en FAT32 o exFAT, y crear una carpeta en su interior llamada "THESPECTRUM", de la que cuelgue "ROMS". y allí dentro estarán las roms en formato <memoria>-x.rom, donde memoria es la memoria de la máquina y x el número de ROM. Por ejemplo, la del Speccy original será simplemente el archivo 48.rom, la del 128k inglés o español, o incluso la del +2 gris, 128-0.rom y 128-1.rom. Finalmente, el Plus3 llevaba cuatro ROMs que serán numeradas del 0 al 3. Si tenemos las ROMS en el subdirectorio speccy_roms de Descargas/, lo haríamos así:

$ mkdir -p THESPECTRUM/ROMS
$ cd THESPECTRUM/ROMS
$ pwd
/run/media/baltasarq/SPECCY/THESPECTRUM/ROMS
$ cp ~/Downloads/speccy_roms/*.rom .
$ ls
128-0.rom  128-1.rom  48.rom  plus3-0.rom  plus3-1.rom  plus3-2.rom  plus3-3.rom
Enter fullscreen mode Exit fullscreen mode

Extraemos el pendrive, conectamos el TheSpectrum, le enchufamos el pendrive, esperamos a que se inicie y... ¡ta-chán! En lugar de esa ROM con el editor del 48k (oh Dios mío), tendremos la ROM escogida según las preferencias de nuestra máquina retro emuladora. En mi caso, los archivos que escogí fueron los del Spectrum 128k de Investrónica, que después se vendió en inglaterra como Spectrum 128k con algunas modificaciones. Suele ser reconocible por la famosa parrilla a su derecha, y que la versión inglesa no llevaba teclado independiente.

El Speccy 128k de Investrónica

Así, en mi caso pude volver a experimentar el arranque de mi Speccy de cuando tenía once años, en aquellas navidades de 1985, con su mensaje de copyright modificado ("ESPAÑOL") y un pequeño pitido indicando el estar preparado. Sería con el manual del Speccy que aprendería a manejarme con BASIC, y acabaría haciéndome informático.

Arranque del Speccy 128k español

Normalmente TheSpectrum arranca en el modo carrousel, donde podemos jugar a cualquier juego, pero podemos configurarlo para que arranque ya en el "modo clásico"... ¡y entonces podemos programar directamente en BASIC, como si fuera el Speccy real!

Usando The Spectrum para programar en BASIC

Decidí hacer una pequeña prueba. ¿Cómo dibujar un árbol fractal en BASIC? Los fractales son dibujos que podrñiamos decir que provienen del mundo matemático, y se popularizaron en los 70 del siglo XX. Son dibujos que se realizan repitiendo un patrón determinado con una escala diferente. En el caso de un árbol fractal, utilizaremos el dibujo de tres rectas en Y para crear el tronco y las dos ramas principales. Si seguimos dibujando estas Y desde las dos ramas, pero utilizando un tamaño más pequeño, tendremos ramas y ramas (creando un "frondoso follaje") hecho a partir de Y cada vez más pequeñas.

Árbol fractal

En el caso de lo que vamos a programar, cada vez la longitud de los segmentos formando la Y será la mitad de la anterior.

El lienzo

El Spectrum está siempre en modo gráfico, y de hecho no existe forma de cambiar a un modo texto "puro" (eso sería un "invento" del IBM PC, todavía por venir). Las coordenadas 0, 0 se sitúan en la esquina inferior izquierda de la pantalla, de forma que si escribimos PLOT 0, 0: DRAW 100, 100 veremos que aparece una línea desde el borde inferior izquierdo hasta casi la mitad de la pantalla. Es decir, las coordenadas son positivas hacia la derecha y hacia arriba, como si estuviéramos viendo el cuadrante positivo de un modelo 2D matemático.

La instrucción PLOT x, y acepta dos coordenadas absolutas, donde x es la posición horizontal e y la vertical. El máximo de resolución se alcanza con x = 256 e y = 192.

La máquina

Programar en The Spectrum no es una experiencia deliciosa, precisamente. En lugar de una carcasa de un Spectrum Plus, o incluso un PLUS 2 o PLUS 3, optaron por la carcasa (y el teclado) del modelo original. Aunque seguro que es muy icónico, es bastante poco confortable. Al margen del tacto del teclado (dead flesh o carne muerta, que era como se conocía dicho tacto en la época), ciertas funcionalidades se obtienen pulsando combinaciones de teclas con Caps Shift (CS) y Symbol shift (SS). Por ejemplo, el borrado de caracteres se obtiene pulsando CS + 0, los cursores para moverse CS + 5 hasta CS + 8, las comillas se obtienen con SS + P, los dos puntos con SS + Z...

Programando en el BASIC del Spectrum

Pero había que hacerlo y se hizo. Como era de esperar, al cabo de veinte minutos ya cometía muy pocos errores.

Guardando y cargando

¿Tenemos que hacerlo todo de una sentada? ¿Y después perder el código?

Afortunadamente, no, Retro Games nos tiene cubiertos aquí. Si guardamos el programa (SAVE guardaría el programa en cinta, con LINE 10 le decimos que se autoejecute en la línea 10):

SAVE "tree.bas" LINE 10
Enter fullscreen mode Exit fullscreen mode

Veremos aparecer los colores del borde según los cuales sabemos que el Speccy original estaría guardando el programa codificado en sonido en una cinta de cassette.

Lo impresionante es que, independientemente de la ROM que estemos usando, en nuestro pendrive se habrá creado un archivo *WRT_tree.bas.tap". Los archivos TAP son archivos que simulan contener archivos del Speccy, como si fueran un contenedor. Podremos cargarlos en un emulador... y en BASINC. Este program simula ser un Spectrum, permitiéndonos programar en BASIC con modernidades como los atajos de teclado habituales... ¡y también cargar archivos TAP con programas BASIC dentro!

BASINC ejecutando nuestro programa

BasinC solo está disponible para Windows, pero funciona perfectamente con Wine.

Si además queremos cargar el trabajo que hayamos realizado previamente en BASIC en el propio TheSpectrum, solo tendremos que ir al modo carrousel y seleccionar el pendrive, marcar el archivo TAP en su interior, y pulsar ENTER para cargarlo.

Lo cierto es que esta parte es algo problemática. Podemos cambiar la configuración de ese TAP (pulsando SPACE), para que se abra en el modelo 128k, pero lo cierto es que no lo he conseguido. Por algún motivo, vuelve a cargar en lo que sería un modelo 48k.

En cualquier caso, ya tenemos dispuesto un entorno de trabajo.

Manos a la obra

En primer lugar, tenemos que "portar" la idea de la función recursiva a las limitaciones de Sinclair BASIC, es decir, si tenemos una función foo()...

function foo(int n)
{
    if ( n == 0 ) {
        return;
    } else {
        console.log( "*" );
        foo( n - 1 );
    }
}
Enter fullscreen mode Exit fullscreen mode

Esta función JavaScript, invocada por ejemplo con n = 3, es decir foo( 3 ), imprime tres asteriscos en la consola. El caso base se produce cuando n = 0, donde se suceden todos los retornos de las llamadas pendientes. El caso regular es imprimir un asterisco y llamarse recursivamente con n - 1.

...entonces lo más parecido en Sinclair BASIC sería...

10  CLS
20  LET n = 3
30  GOSUB 100
40  STOP
100 REM foo
110 IF n = 0 THEN RETURN
120 PRINT "*";
130 LET n = n - 1
140 GOSUB 100
Enter fullscreen mode Exit fullscreen mode

Lo que lo hace más lioso, sin duda, son dos cuestiones: a) los números de línea (ya que hay que organizarse, intentando dejar suficientes "huecos" entre ellos), y b) solo hay variables globales, por lo que n no puede ser una variable local de foo. Simplemente, Sinclair BASIC no soporta esa posibilidad.

El código fuente

Como no tenemos variables locales, necesitaremos una pila donde guardaremos la longitud anterior de los segmentos cada vez que realicemos una llamada recursiva. Es decir, al principio los segmentos se dibujarán con tamaño 50, después 25, después 12, 6, 3... eso quiere decir que iremos guardando en la pila 50, 25, 12, 6, y 3. ¿Por qué? Porque cuando dibujemos cada una de las dos ramas de la Y, llamaremos recursivamente a nuestra rutina para que vuelva a pintar, con un tamaño más pequeño, otras dos Y.

Un truco importante es que, para volver a la unión de las dos ramas lo único que podemos hacer es movernos de nuevo tanto como dibujamos en un principio. Es decir, que si dibujamos con DRAW 0, 50 para el tronco principal, y DRAW -50, 50 para la rama izquierda, tendremos que redibujar DRAW 50, -50 para volver a la unión entre las ramas y dibujar con DRAW 50, 50 la rama derecha. Después con DRAW -50, -50 volvemos a la unión de las ramas, y con DRAW 0, -50 volvemos a la posición inicial de dibujo del tronco.

dibuja_Y():
    if long < 2
        return
    end

    draw 0, long
    draw -long, long    // Left branch
    push(long)

    long /= 2
    dibuja_Y()

    pop(long)
    draw long, -long    // Back to the branches union
    draw long, long     // Right branch
    push(long)

    long /= 2
    dibuja_Y()
    pop(long)
    draw -long, long    // Back to the branches union
    draw 0, -long       // Back to the beginning 
end
Enter fullscreen mode Exit fullscreen mode

¿Podemos evitar los guardados (push) de long y sus recuperaciones (pop) haciendo el cálculo inverso? Es decir, si hacemos la llamada recursiva con long / 2, podemos recuperar el valor original con long * 2, ¿no? El problema es que no todos los valores de long van a ser pares, con lo cual las inexactitudes al convertir el valor a entero para poder dibujar desbaratarán nuestros planes. Además, haciéndolo así podemos experimentar y cambiar la reducción de long mediante la división entre dos por la multiplicación por 0.75, por ejemplo.

El comienzo es situarnos aproximadamente en la mitad inferior de la pantalla y crear las variables necesarias. No pongo los números de línea que no son destino de GO SUB para no sobrecargar el listado.

10 REM Variables
   LET depth=30: LET long=40
   LET lpos=0
   DIM l(depth)
50 REM Start point
   PLOT 125,0
   REM Empezar a pintar
   GO SUB 1000
Enter fullscreen mode Exit fullscreen mode

Una vez que hemos creado las variables, podemos empezar a pintar. Nótese que depth lleva la profundidad máxima del dibujo fractal (o lo que es lo mismo, hasta cuántas veces es capaz de llamarse a sí mismo para pintar), ya que es el tamaño de la pila. La variable long lleva la longitud para los segmentos de la Y, que al principio es de 40 (debemos contemplar hasta cuán lejos puede llegar a dibujar el algoritmo, ya que es posible que nos salgamos de la pantalla.) Las variables l y lpos están muy relacionadas: son nuestra pila. En l se guarda el contenido de la variable long antes de reducirlo y llamarse recursivamente para seguir dibujando. La variable lpos guarda la última posición guardada en el vector l.

3000 REM Push
     LET lpos=lpos+1: LET l(lpos)=long
     RETURN 
3100 REM Pop
     LET long=l(lpos): LET lpos=lpos-1
     RETURN 
3200 REM Reduce long
     LET long=INT(long/2)
Enter fullscreen mode Exit fullscreen mode

Las líneas comentadas como Push guardan la nueva longitud de long en el vector l. De forma complementaria, las líneas comentadas como Pop recogen el último valor guardado en l.

El listado de diubjo completo aparece justo debajo.

1000 REM Draw
     IF long<2 THEN RETURN 
     DRAW 0,long             :REM Trunk
     DRAW -long-2,long       :REM Left branch
     GO SUB 3000             :REM Push
     GO SUB 3200             :REM Reduce long
     GO SUB 1000             :REM rec call Draw
     GO SUB 3100             :REM Pop
     DRAW long+2,-long       :REM Undo left branch
     DRAW long+2,long        :REM Right branch
     GO SUB 3000             :REM Push
     GO SUB 3200             :REM Reduce long
     GO SUB 1000             :REM rec call Draw
     GO SUB 3100             :REM Pop
     DRAW -long-2,-long      :REM Undo right branch
     DRAW 0,-long            :REM Undo trunk
     RETURN 
Enter fullscreen mode Exit fullscreen mode

Finalmente, aquí está el listado completo.

10 BORDER 7: PAPER 7: INK 0: CLS 
20 LET depth=30: LET long=30
30 LET lpos=0
40 DIM l(depth)
50 REM Start point
60 PLOT 125,0
100 GO TO 9000
110 STOP 
1000 REM Draw
1005 IF long<2 THEN RETURN 
1007 DRAW 0,long
1010 DRAW -long-2,long
1020 GO SUB 3000
1030 GO SUB 3200
1040 GO SUB 1000
1050 GO SUB 3100
1060 DRAW long+2,-long
1070 DRAW long+2,long
1080 GO SUB 3000
1090 GO SUB 3200
1100 GO SUB 1000
1110 GO SUB 3100
1120 DRAW -long-2,-long
1130 DRAW 0,-long
2000 RETURN 
3000 REM Push
3010 LET lpos=lpos+1: LET l(lpos)=long
3020 RETURN 
3100 REM Pop
3110 LET long=l(lpos): LET lpos=lpos-1
3120 RETURN 
3200 REM Reduce long
3210 LET long=INT (long*0.6)
3220 RETURN 
9000 REM Rec entry point
9020 GO SUB 1000
Enter fullscreen mode Exit fullscreen mode

Solo resta aclarar que al comienzo del programa movemos la ejecución a la línea 9000 para que termine con una ejecución correcta u Ok, 9020:1. De otra forma, si sustituimos la línea 100 por 100 go sub 1000, el programa terminará con el error "Sentencia STOP, 110:1"

Speccy rulez!!

Top comments (0)