jueves, 12 de marzo de 2026

ZB2SB. Paso 1. Definir el Lenguaje de origen

Gramática ZX BASIC Spectrum 48K — BNF completa y explicada

Índice de entradas del conversor

Modificado el 12/03/2026, ampliaciones en color rojo



Gramática del ZX BASIC del Spectrum 48K

Esta entrada ofrece un resumen estructurado de la gramática del ZX BASIC del Sinclair ZX Spectrum 48K, basado en las referencias originales del manual oficial del Spectrum y las versiones digitalizadas del manual en español. Primero se presenta una versión ligera y, después, la definición formal completa.

  • BNF/EBNF práctica, apta para implementar (recursive descent o LL(1) con pequeñas adaptaciones).
  • Las restricciones del hardware (p. ej. rango de números de línea) se indican en comentarios.
  • Se prioriza claridad en la introducción; los detalles formales aparecen en el BNF completo final.

NOTA IMPORTANTE: heredado de los ZX80/81 con solo 1 KB disponible, para ahorrar memoria todas las palabras clave se almacenan internamente como tokens de 1 byte. Por tanto, toda sentencia debe comenzar obligatoriamente por un token de palabra clave. No puede comenzar por un identificador. LET es obligatorio siempre. REM solo se reconoce si es exactamente REM.

Estos ajustes son mínimos pero críticos para definir correctamente la gramática y la generación de tokens.


1. Estructura de un programa

Un programa en ZX BASIC está compuesto por líneas numeradas:

<linea> ::= <numero> <espacio> <instruccion>
  • Los números de línea van de 1 a 9999.
  • Una línea puede contener una o varias instrucciones separadas por :.

2. Tipos de datos

2.1 Tipos básicos

  • Números: coma flotante de 40 bits.
  • Cadenas: delimitadas por comillas ("texto").
  • No existen enteros ni booleanos como tipos nativos; la lógica usa 0 para falso y <>0 para cierto.

2.2 Nombres de variables

<var_num> ::= <letra> [ <letra_o_digito> ]
<var_str> ::= <letra> [ <letra_o_digito> ] "$"
<var_array> ::= <var> "(" <lista_expresiones> ")"
  • Las variables numéricas y de cadena con el mismo nombre son distintas.
  • Las matrices pueden ser numéricas o de cadena.

3. Expresiones

3.1 Expresiones numéricas

Precedencia de operadores (de mayor a menor):

  1. Unario: -
  2. Potencia: ^
  3. Multiplicación / División: * /
  4. Suma / Resta: + -
  5. Relacionales: =, <, >, <=, >=, <>
  6. Lógicos: AND, OR, NOT

Fuentes: capítulos 3, 7 y 13 del manual.

3.2 Expresiones de cadena

  • Concatenación: "A" + "B".
  • Slicing: A$(n TO m).
  • Longitud: LEN A$.

Fuente: capítulo 8 del manual.


4. Sentencias

4.1 Asignación

<asignacion> ::= LET <var> = <expresion>

LET es obligatorio en el Spectrum 48K (no puede omitirse).

4.2 Entrada y salida

  • PRINT
  • INPUT
  • CLS
  • TAB(n), AT y,x

Fuente: capítulos 2 y 15 del manual.


5. Control de flujo

5.1 IF

IF <expresión> THEN <instrucciones>

Ejecuta todas las instrucciones (separadas por :) hasta fin de línea. No existe ELSE en el ZX Spectrum 48K.

5.2 Bucles FOR

FOR <var> = <expr> TO <expr> [STEP <expr>]
...
NEXT <var>

Fuente: capítulo 4 del manual.

5.3 Subrutinas

GOSUB número
RETURN

Fuente: capítulo 5 del manual.

5.4 Saltos

  • GO TO
  • STOP
  • CONTINUE

6. Datos

READ <lista_variables>
DATA <lista_constantes>
RESTORE [número]

Fuente: capítulo 6 del manual.


7. Comentarios

En ZX Spectrum 48K, la sentencia REM admite exactamente dos formas:

  • Comentario vacío: REM seguido inmediatamente de fin de línea.
  • Comentario con contenido: REM seguido de un ESPACIO y, a continuación, cualquier número de caracteres (incluidos espacios) hasta fin de línea.

Ejemplos válidos:

10 REM
20 REM Hola mundo
30 REM  Más espacios al inicio del comentario

Ejemplos inválidos:

40 REMHola     ; falta el espacio tras REM
50 REMHola ; tabulación inmediata tras REM (no permitido)

EBNF:

ESPACIO      = " ";
EOL          = "\n";
anyCharacter = ? cualquier carácter Unicode ? ;

remStmt =
      "REM"
    | "REM" , ESPACIO , { anyCharacter } ;

8. Funciones incorporadas

Matemáticas

ABS, INT, SGN, SIN, COS, TAN, ASN, ACS, ATN, SQR, EXP, LN, PI …

Cadenas

LEN, STR$, VAL, CHR$, CODE

Sistema

PEEK, POKE, USR, IN, OUT, RND, RANDOMIZE

Fuente: capítulos 9, 10, 11, 14 y 23 del manual.


9. Arrays

DIM A(10)
DIM N$(5)
  • Índices desde 1 hasta el tamaño declarado (arrays base 1).
  • Acceso: A(1), A(5), N$(1)…

10. Gráficos

  • PLOT x,y
  • DRAW dx,dy
  • CIRCLE x,y,r
  • POINT x,y

Fuente: capítulo 17 del manual.


11. Colores

  • INK n
  • PAPER n
  • FLASH n
  • BRIGHT n
  • INVERSE n
  • OVER n
  • BORDER n

Fuente: capítulo 16 del manual.


12. Sonido

BEEP duracion, tono

Fuente: capítulo 19 del manual.


13. Cinta y ficheros

  • SAVE
  • LOAD
  • MERGE
  • VERIFY

Fuente: capítulo 20 del manual.


14. Gramática mínima para transpiladores

<program> ::= { <line> }

<line> ::= <number> <Statement>

<Statement> ::= <SimpleStatement> { ":" <SimpleStatement> }

<SimpleStatement> ::= <assign>
                     | PRINT <print_list>
                     | INPUT <var_list>
                     | IF <expr> THEN <Statement>
                     | FOR <assign> TO <expr> ["STEP" <expr>]
                     | NEXT <var>
                     | GOSUB <number>
                     | RETURN
                     | GO TO <number>
                     | READ <var_list>
                     | DATA <const_list>
                     | DIM <dim_list>
                     | PLOT | DRAW | CIRCLE | POINT | ...

15. BNF/EBNF del ZX BASIC del Spectrum 48K (versión corregida)

1) Léxico

letter        = "A".."Z" | "a".."z"
digit         = "0".."9"
nonZeroDigit  = "1".."9"
alnum         = letter | digit

numVar        = letter , [ alnum ]
strVar        = letter , [ alnum ] , "$"
var           = numVar | strVar

intLiteral    = digit , { digit }
fractPart     = "." , { digit }
expPart       = ("E" | "e") , [ "+" | "-" ] , digit , { digit }

numLiteral    = intLiteral , [ fractPart ] , [ expPart ]
              | fractPart , [ expPart ]

ESPACIO      = " "
EOL          = "\n"
anyCharacter = ? cualquier carácter Unicode ?

strLiteral    = '"' , { anyCharacter } , '"'


; REM solo se reconoce como palabra clave si el token es exactamente "REM".
; Un identificador NO puede iniciar una sentencia.

2) Estructura del programa

program       = { line }

line          = lineNumber , Statement
lineNumber    = LineNumberToken
LineNumberToken = nonZeroDigit , [ digit ] , [ digit ] , [ digit ]
(* Rango efectivo: 1..9999; token específico generado por el lexer *)

3) Statement y SimpleStatement

Statement     = SimpleStatement , { ":" , SimpleStatement }

; SimpleStatement debe comenzar por token de palabra reservada

SimpleStatement =
                   letStmt
                 | printStmt
                 | inputStmt
                 | ifStmt
                 | forStmt
                 | nextStmt
                 | gotoStmt
                 | gosubStmt
                 | returnStmt
                 | stopStmt
                 | continueStmt
                 | readStmt
                 | dataStmt
                 | restoreStmt
                 | dimStmt
                 | clsStmt
                 | graphicStmt
                 | colorStmt
                 | soundStmt
                 | tapeStmt
                 | remStmt

4) Expresiones

expr          = logicOr
logicOr       = logicAnd , { "OR" , logicAnd }
logicAnd      = relation , { "AND" , relation }

relation      = arithExpr , [ relOp , arithExpr ]
relOp         = "=" | "<>" | "<" | ">" | "<=" | ">="

arithExpr     = term , { ("+" | "-") , term }
term          = power , { ("*" | "/") , power }
power         = unary , { "^" , unary }

unary         = [ "-" | "NOT" ] , primary

primary       = numLiteral
              | strLiteral
              | varRef
              | funcCall
              | "(" , expr , ")"

5) Variables y arrays (base 1)

varRef        = arrayRef | var

arrayRef      = ( numVar | strVar ) ,
                "(" , indexExpr , { "," , indexExpr } , ")"

indexExpr     = expr

6) Funciones

funcCall      = funcNum | funcStr | sysFunc

funcNum       = ("ABS" | "INT" | "SGN" | "SIN" | "COS" | "TAN"
                | "ASN" | "ACS" | "ATN" | "SQR" | "EXP" | "LN"
                | "VAL" | "PI")
                "(" , [ expr ] , ")"

funcStr       = "LEN" "(" , ( strVar | strLiteral ) , ")"
              | "STR$" "(" , expr , ")"
              | "CHR$" "(" , expr , ")"

sysFunc       = "PEEK" "(" , expr , ")"
              | "USR"  "(" , expr , ")"
              | "CODE" "(" , (strVar | strLiteral) , ")"

7) Sentencias (detalle)

remStmt   = "REM"
          | "REM" , ESPACIO , { anyCharacter }
 
letStmt   = "LET" , ( varRef | var ) , "=" , expr
printStmt = "PRINT" , [ printList ]
printList = printItem , { ("," | ";") , printItem }
printItem = expr
          | "TAB" "(" , expr , ")"
          | "AT" expr "," expr

inputStmt = "INPUT" , varList
varList   = varRef , { "," , varRef }

ifStmt    = "IF" , expr , "THEN" , Statement

forStmt   = "FOR" , numVar , "=" , expr , "TO" , expr , [ "STEP" , expr ]
nextStmt  = "NEXT" , [ numVar ]

gotoStmt  = ("GOTO" | "GO TO") , lineNumber
gosubStmt = ("GOSUB" | "GO SUB") , lineNumber
returnStmt= "RETURN"

stopStmt  = "STOP"
continueStmt = "CONTINUE"

readStmt  = "READ" , varList
dataStmt  = "DATA" , constList
constList = constItem , { "," , constItem }
constItem = numLiteral | strLiteral

restoreStmt = "RESTORE" , [ lineNumber ]

dimStmt   = "DIM" , dimList
dimList   = dimItem , { "," , dimItem }
dimItem   = ( numVar | strVar ) , "(" , sizeExpr , { "," , sizeExpr } , ")"
sizeExpr  = expr

clsStmt   = "CLS"

graphicStmt = plotStmt | drawStmt | circleStmt | pointStmt
plotStmt  = "PLOT" , expr , "," , expr
drawStmt  = "DRAW" , expr , "," , expr
circleStmt= "CIRCLE" , expr , "," , expr , "," , expr
pointStmt = "POINT" , expr , "," , expr

colorStmt = ("INK" | "PAPER" | "FLASH" | "BRIGHT" | "INVERSE" | "OVER" | "BORDER") , expr

soundStmt = "BEEP" , expr , "," , expr

tapeStmt  = ("SAVE" | "LOAD" | "VERIFY" | "MERGE") , strLiteral

16. Referencias

miércoles, 11 de marzo de 2026

ZB2SB. Conversor de Basic de los ZX al Super Basic del QL (Reinicio)

Índice de entradas del conversor


📝 Diseño de un Transpilador ZX Spectrum BASIC → QL SuperBASIC

Reinicio del proyecto (esta vez espero acabarlo…)

Retomo este proyecto con la intención firme de completarlo. Siempre he querido disponer de una herramienta capaz de traducir programas escritos en lenguaje ZX Basic del Spectrum al lenguaje SuperBASIC del Sinclair QL. Aunque ya he empezado varias veces, esta vez quiero estructurarlo bien para poder llegar al final. La idea es seguir una serie de pasos claros, desde la definición del alcance hasta la ejecución de pruebas reales.

Aunque ambos dialectos BASIC comparten muchos conceptos, tienen diferencias importantes que obligan a realizar transformaciones no triviales. En esta entrada describo la arquitectura general de un transpilador, enfocada a este caso concreto.


1) Definir el alcance y las variantes

✔ Lenguaje de origen

ZX BASIC (Spectrum):

  • Líneas numeradas
  • GOTO, GOSUB, RETURN
  • LET, RANDOMIZE, PLOT, INK, PAPER
  • Tokens gráficos
  • PEEK/POKE, USR
  • Comentarios con REM o '

✔ Lenguaje destino

Sinclair QL SuperBASIC:

  • No requiere números de línea
  • Bloques IF…END IF, REPeat, SELect ON
  • Procedimientos (PROCedure) y funciones (DEFine FuNction)
  • I/O con canales (OPEN, PRINT #ch)
  • Gráficos con MODE, LINE, POINT

✔ Compatibilidad y variantes

  • Compatibilidad con ZX80/81 BASIC
  • Futuro soporte para Spectrum 128/+2/+3
  • Ajuste de coordenadas y resoluciones

✔ Cobertura funcional

Cobertura mínima:

  • Asignaciones y expresiones
  • Estructuras: IF, FOR, WHILE, GOTO, GOSUB
  • Arrays
  • PRINT / INPUT
  • Matemáticas básicas

Cobertura ampliada:

  • Gráficos y sonido
  • MERGE, LOAD, SAVE
  • ON ERROR, ON x GOSUB
  • Manejo de cadenas
  • Temporización / canales I/O

No soportado:

  • PEEK, POKE, USR

2) Reunir / normalizar las gramáticas

  • Gramática del ZX BASIC: keywords, literales, tokens gráficos, comentarios.
  • Gramática del SuperBASIC usada para validar la salida.

3) Arquitectura general del transpilador

  1. Lexer: obtener tokens.
  2. Parser + AST: nodos Program, If, For, Print, etc.
  3. Análisis semántico:
    • Tabla de símbolos
    • Detección de subrutinas
    • Control de flujo (CFG)
    • Tipos y arrays
  4. Transformación del AST:
    • Eliminar números de línea
    • Reescribir saltos
    • GOSUB → PROCEDURE
    • Normalización I/O
    • Conversión gráfica
  5. Generación de código SuperBASIC
  6. Post‑procesado

4) Reglas de mapeo

4.1 Estructura y control de flujo

  • IF…THEN…ELSEIF…END IF
  • WHILE/WENDREPeat + EXIT WHEN
  • GOTO → intentar estructurar
  • GOSUBPROCedure

4.2 Variables y arrays

  • BASIC Spectrum: base 1 → QL: base 0
  • Cadenas $
  • Ajuste de rangos en DIM

4.3 Entrada/Salida

  • PRINT / PRINT #ch
  • INPUT / INPUT #ch
  • Uso de ; y ,

4.4 Funciones

  • SIN, COS, ABS, RND
  • RANDOMIZE
  • VAL, STR$, LEFT$, RIGHT$

4.5 Gráficos y sonido

  • Spectrum: PLOT, DRAW, CIRCLE
  • QL: POINT, LINE, CIRCLE
  • Atributos: sin equivalente

4.6 Memoria

  • PEEK, POKE, USR → no portables

4.7 Errores

  • ON ERROR → comportamiento diferente

5) Estrategia para convertir GOTO/GOSUB

  • Construir un CFG
  • Buscar patrones:
    • If‑then → IF…END IF
    • Saltos hacia atrás → loops
    • Subrutinas → PROCedure
  • Modo compatibilidad si no es seguro

6) Conjunto mínimo de casos de prueba

  • Asignaciones
    10 LET a=1: LET b=a+2: PRINT a+b
  • IF simple
    10 IF a>10 THEN PRINT "OK"
  • IF con ELSE
    10 IF a>10 THEN
    20 PRINT "OK"
    30 ELSE PRINT "NO"
    40 END IF
  • FOR/NEXT
    10 FOR i=1 TO 10: PRINT i: NEXT i
  • WHILE/WEND
    10 LET i=0
    20 WHILE i
  • GOSUB/RETURN
    10 LET x=5: GOSUB 90: PRINT x: STOP
    90 LET x=x*2: RETURN
  • Arrays y cadenas
    10 DIM a(10): FOR i=1 TO 10: LET a(i)=i*i: NEXT i: PRINT a(5)
    20 LET s$="HELLO": PRINT LEN s$, RIGHT$(s$,2)
  • Gráficos
    10 PLOT 10,10: DRAW 20,0: CIRCLE 30,30,10

7) Ejemplo de traducción básica

Entrada ZX Spectrum BASIC:

10 REM Calculo de suma
20 LET s=0
30 FOR i=1 TO 10
40 LET s=s+i
50 NEXT i
60 IF s>50 THEN PRINT "MAYOR"; ELSE PRINT "MENOR O IGUAL";
65 PRINT " DE 50"
70 GOSUB 90
80 PRINT "FIN": STOP
90 PRINT "TOTAL=";s: RETURN

Salida QL SuperBASIC:

REM Calculo de suma
s = 0
FOR i = 1 TO 10
  s = s + i
END FOR
IF s > 50 THEN
  PRINT "MAYOR";
ELSE
  PRINT "MENOR O IGUAL";
END IF
PRINT " DE 50"
PROC_show_total(s)
PRINT "FIN"
STOP

PROCedure PROC_show_total(total)
  PRINT "TOTAL="; total
END PROCedure

8) Ejemplo de posible mapeo de gráficos

Spectrum:

10 INK 2: PAPER 0: CLS
20 PLOT 10,10: DRAW 50,0
30 CIRCLE 40,40,10

QL:

INK 2 : PAPER 0 : CLS
POINT 10,10
LINE 10,10 TO 60,10
CIRCLE 40,40,10

9) Ejemplo de traducción (no trivial)

10 REM Serie, demo graficos y subrutina
20 LET s=0: LET x=64: LET y=64
30 FOR i=1 TO 5
40 LET s=s+i
50 NEXT i
60 IF s>10 THEN PRINT "MAYOR";: PRINT " "; s ELSE PRINT "NO";: PRINT " "; s
70 PLOT 10,10: DRAW 20,0: DRAW 0,20: DRAW -20,0: DRAW 0,-20
80 GOSUB 200
90 DATA 3,5,8
100 READ a,b,c
110 PRINT "DATA:"; a; ","; b; ","; c
120 STOP
200 REM duplica s
210 LET s=s*2
220 RETURN

Salida QL:

100 REMark Calculo de suma
110 REMark Serie, demo graficos y subrutina
120 CLS
130 s=0
140 x=64
150 y = 64
160 FOR i = 1 TO 5
170 s = s + i
180 END FOR i
190 IF s > 10 THEN
200   PRINT "MAYOR";
210   PRINT " "; s
220 ELSE
230   PRINT "NO";
240   PRINT " "; s
250 END IF

270 REMark Gráficos: PLOT y DRAW relativo
280 INK 7 : PAPER 0
290 __gx = 10 : __gy = 10
300 POINT __gx, __gy
310 LINE __gx, __gy TO __gx + 20, __gy
320 LINE __gx, __gy TO __gx, __gy + 20
330 LINE __gx, __gy TO __gx - 20, __gy
340 LINE __gx, __gy TO __gx, __gy - 20

350 PROC_duplica_s(s)

370 _DATA_INIT
390 FOR i=1 TO 3
400   PRINT _READ
410 END FOR i

450 DEFine PROCedure PROC_duplica_s(by_s)
460   s = s * 2
470 END DEFine

490 DEFine PROCedure _DATA_INIT
500   Data_Init = 0
510 END DEFine

530 DEFine FuNction _READ
540   IF Data_Init <> 1 THEN _DATA
560   __DATA(0) = __DATA(0) + 1
570   RETurn __DATA(__DATA(0))
580 END DEFine _READ

600 DEFine PROCedure _DATA
620   Data_Init = 1
630   DIM __DATA(3)
640   __DATA(0) = 0
650   __DATA(1) = 3
660   __DATA(2) = 5
670   __DATA(3) = 8
680 END DEFine

martes, 3 de marzo de 2026

Esperar y Soñar: La Magia Olvidada de los Juegos en Cassette

Esperar y Soñar: La Magia Olvidada de los Juegos en Cassette


Hubo un tiempo en el que jugar no empezaba con un botón que decía Play, sino con un ritual. Un rito lento, sonoro y casi hipnótico: colocar el cassette, ajustar el volumen y cruzar los dedos para que la carga llegara al 100% sin errores. En la era de la inmediatez, ese proceso parece casi absurdo, pero para muchos fue la puerta de entrada a mundos increíbles, aunque estuvieran hechos de ocho colores y píxeles del tamaño de un puño.

En esta entrada quiero rendir homenaje a una de las experiencias más icónicas —y a menudo olvidadas— de los queridos ordenadores de 8 bits: la carga de juegos en cassette.


El sonido que encendía la imaginación

Dicen que la nostalgia también tiene sonido. Para muchos, es una serie de pitidos chirriantes que se repetían durante varios minutos. Aquellos ruidos que hoy nos suenan caóticos eran, en realidad, una melodía digital única. Cada máquina tenía su propio patrón, y dentro de ellas, cada juego su propio “canto”. Había quien presumía en el patio del colegio de distinguir un Spectrum de un MSX solo por el sonido de carga (un talento inútil, pero que en aquella época sonaba a magia).

Lo que se grababa en la cinta eran pitidos: una frecuencia representaba el cero y otra el uno. Debían ser audibles porque las cintas domésticas no eran capaces de registrar señales digitales directas. El concepto venía heredado de los módems y de las comunicaciones por línea telefónica: transformar bits en tonos que cualquier magnetófono pudiera grabar. Sencillo, ingenioso y tremendamente limitado, pero funcionaba. 

Pantalla de carga en un Spectrum (Fuente: retrogaming)


El arte de cargar un juego

Cargar un juego no era trivial. Había que seguir una serie de pasos casi ceremoniales, y cada sistema tenía sus manías y peculiaridades.

  • Conectar el cassette. Cada ordenador usaba un sistema de lectura distinto. Podemos agruparlos en dos grandes tipos:
    • Unidades analógicas: eran las normales de casa. La CPU del ordenador se encargaba de procesar los tonos grabados en la cinta, a veces con ayuda de chips como la ULA en el Spectrum. Esto permitía modificar las rutinas de carga para acelerar el proceso (cargas TURBO) o añadir sistemas de protección anticopia. Pero tenía una limitación: mientras cargaba, el ordenador estaba “secuestrado”.
      • ZX Spectrum y MSX: usaban grabadoras domésticas o walkman. Se conectaban por cables de audio para lectura y grabación, y en el caso de MSX un tercer cable activaba el motor automáticamente. Era habitual pelearse por la grabadora familiar, y nacieron unidades como el Computone, que prometían “cargar siempre a la primera”. No era del todo cierto, pero ayudaba.
      • Amstrad CPC 464: incluía una unidad integrada igual a una doméstica, pero más estable. No hacía el sonido estridente del Spectrum y controlaba el motor directamente, lo que evitaba muchos errores.
      • Amstrad CPC 6128: incluía disquetera interna, pero mantenía conectores para usar una grabadora doméstica si era necesario.
    • Unidades digitales: en ellas, la grabadora incorporaba un circuito que convertía los tonos de la cinta en ceros y unos antes de enviarlos al ordenador. Esto liberaba a la CPU y permitía pequeñas maravillas, como minijuegos que se ejecutaban mientras el juego principal cargaba.
      • Commodore y Atari: usaban unidades dedicadas, no servían para música, pero eran fiables y difíciles de “desajustar”. Aunque podían fallar, eran mucho más estables que las analógicas.
  • El volumen perfecto. En las cargas analógicas era vital. Muy alto, saturaba; muy bajo, no distinguía las frecuencias. La delgada línea entre jugar o encontrarte con el maldito Tape loading error.
  • La paciencia. Los tiempos de carga solían ser de 3 a 10 minutos, pero algunos juegos superaban los 20.
  • La esperanza. Si fallaba al 97% tocaba empezar de cero. ¿Frustrante? Sí. ¿Épico? También. Y aun así, lo repetíamos una y otra vez. 

Un Walkman de Sony (Fuente: amazonaws.com)

 

El muy deseado Computone (fuente: teknoplof.com)

Casetera del Commodore-64 (Fuente: pinimg.com)


Cuando la carga era parte del juego

Algunos desarrolladores convirtieron la pantalla de carga en un espectáculo. Una oportunidad para entretener al jugador mientras los datos fluían desde la cinta:

  • Pantallas multicolor en el Spectrum, auténticas obras de arte que aprovechaban las limitaciones para crear efectos vibrantes.
  • Mini-demos o barras animadas que mostraban el progreso real del cargado.
  • Cargas turbo, cada vez más veloces y experimentales.
  • Mini-juegos, especialmente en Commodore 64 o Atari ST, donde podías jugar mientras el juego “gordo” seguía cargando.

Algunos títulos mostraban arte, mapas o información del juego durante la carga: las primeras loading screens de la historia, con personalidad propia.

La piratería "artesanal"

Las cintas se podían copiar fácilmente. ¿Quién no tuvo un amigo que pasaba horas duplicando juegos con una doble pletina? O aquellas recopilaciones caseras con títulos escritos a mano, a veces con esmero, a veces con letra imposible.

Era otro mundo: más inocente, más casero y más emocionante. Y aquel caos doméstico impulsó a muchos desarrolladores a idear elaborados sistemas anticopia (tema que trataste en tu entrada anterior). 

Unidad de doble pletina, un lujo para las copias. (Fuente: dreamstime.com)


¿Por qué recordarlo hoy?

En un tiempo donde todo está a un clic, recordar la era del cassette nos devuelve una verdad sencilla: las cosas que cuestan se disfrutan más.

Cargar un juego era parte de la aventura. Un preludio que hacía que cada partida supiera a triunfo. No era perfecto, pero era auténtico. Y por eso permanece en la memoria.


¿Y tú? ¿Qué recuerdos tienes?

¿Tenías un Spectrum, un CPC, un MSX o un Commodore 64?
¿A qué juego jugaste primero en cassette?
¿Alguna carga fallida al 97% que aún recuerdes con escalofríos?

Me encantará leer tus anécdotas en los comentarios.

Enlaces recomendados

lunes, 2 de marzo de 2026

Los códigos de protección anticopia en la era de los 8 bits y 16 bits

Descubre los métodos anticopia más creativos y sorprendentes de los años 80 y 90: ruedas de códigos, Lenslok, sectores ilegales, cargadores turbo y otras técnicas míticas de la informática retro.

🛡️ Los códigos de protección anticopia más ingeniosos de la era de los 8 y 16 bits

En los años 80 y principios de los 90, cuando los ordenadores domésticos vivían su edad de oro, la batalla contra la piratería se libraba con imaginación, bordes técnicos y una buena dosis de pillería creativa. Los videojuegos se distribuían primero en cinta y, más tarde, también en disquete: soportes sencillos de duplicar. Las compañías desarrollaron algunos de los sistemas anticopia más extravagantes, ingeniosos y —en ocasiones— delirantemente complicados que se recuerdan en la historia del software.

Este artículo repasa algunos de los métodos más famosos —y otros quizá menos conocidos— que definieron toda una época.


🎨 1. Códigos impresos en color imposible

Para evitar que los manuales se fotocopiaran fácilmente, muchas compañías imprimían contraseñas en combinaciones de color difíciles de reproducir. Las fotocopiadoras de la época convertían ciertos colores en manchas negras o ilegibles, lo que impedía usar copias piratas. Este método también se empleó en tarjetas como las de Jet Set Willy, diseñadas para ser difíciles de duplicar.

 
Manual del Jet Set Willy con sus códigos de colores. (Fuente: computing history org)

 

📘 2. Ruedas de códigos (Code Wheels)

Compañías como Lucasfilm Games o Sierra popularizaron las icónicas ruedas de cartón multicapa que permitían alinear símbolos o imágenes para generar claves únicas. Títulos como The Secret of Monkey Island lo usaron en todas sus versiones.

 

Dial‑A‑Pirate del juego The Secret of Monkey Island –  (Fuente: OldGames.sk)
 
Monkey Island 2 – Mix’n’Mojo Code Wheel (Fuente: oldgames.sk)
The Secret of Monkey Island – Amiga (Fuente: OpenRetro.org)


 

🔬 3. Gráficos 3D y estereogramas

A finales de los 80 aparecieron protecciones visuales que requerían intervención humana directa. Algunos juegos mostraban patrones deformados legibles sólo con lentes incluidas en la caja o con gafas 3D anaglifo rojo‑azul. Sin ese accesorio, la clave era indescifrable.

Lenslok en un juego de MSX (Fuente: msx.org)

🔐 4. Tablas de códigos interminables

Se incluían libretos con enormes tablas repletas de valores en filas y columnas. El juego pedía una intersección concreta, obligándote a usar el original. Bard’s Tale III es un ejemplo bien documentado; en preservación existen tanto tablas como ruedas asociadas a distintas ediciones.

 

La rueda del Bard's Tale III, en forma de rueda pero es una tabla realmente. (Fuente: mocagh.org)

🧪 5. El infame Lenslok

El Lenslok fue uno de los sistemas anticopia más polémicos: un prisma que reorganizaba visualmente un código deformado en pantalla. Entre sus problemas documentados estaban la calibración complicada, la incompatibilidad con pantallas muy grandes o muy pequeñas y la existencia de variantes distintas por juego. [3], [4] wiki/Lenslok)

Dispositivo listo para usar (Fuente io.wp.com)

El dispositivo sin plegar con sus instrucciones (Fuente: ComputingHistory.org)

Otro dispositivo sin plegar (Fuente: SpectrumComputingHistory.org)

 

 

💽 6. Sectores y pistas “ilegales” en disquete

En Amiga, Atari ST y C64 se emplearon técnicas basadas en manipular físicamente el contenido del disco: pistas fuera de norma, sectores con longitudes no generables por hardware doméstico o marcas magnéticas alteradas. En el ecosistema Spectrum/CPC destacó Speedlock, con variantes bien documentadas.

 

Speedlock 1987 (Fuente: https://muckypaws.com) 

📼 7. Cargadores turbo con errores deliberados

En las cintas de ZX Spectrum o Amstrad CPC, los cargadores incluían anomalías intencionadas (cabeceras corruptas, pausas imposibles o bloques “dañados”) que engañaban a las copiadoras. Sistemas como Speedlock en cinta fueron clave en esta técnica.

Pantalla de carga del juego Boina Verde en un CPC (Fuente: video-games-museum.com)


🎤 8. Códigos ocultos en pistas de audio

Algunos lanzamientos poco comunes escondían códigos como sonidos digitales embebidos en la propia cinta. Parecían ruido estático, pero el programa podía interpretarlo como datos válidos. La copia directa de alta calidad era prácticamente el único modo de preservarlos.

🕶️ 9. Libretos imprescindibles para resolver puzzles

Las aventuras gráficas a menudo requerían consultar objetos físicos incluidos en la caja: notas manuscritas, mapas o pistas visuales. El “diario del Grial” de Indiana Jones and the Last Crusade es el ejemplo más célebre.

 

Manual de Indicana Jones y la última cruzada. El diario del Grial.

 



📌 Conclusión: creatividad contra creatividad

Sin conexión a internet, DRM ni activaciones online, las compañías recurrieron a la inventiva física: ruedas, lentes prismáticas, tintas imposibles, pistas alteradas y libretos clave. Hoy, estas protecciones se contemplan con nostalgia: objetos coleccionables que recuerdan una época más artesanal, humana e ingeniosa.


📚 Referencias

Si quieres profundizar, puedes leer información adicional en alguno de los siguientes enalces, en español y en inglés:
  • En español

    • 3djuegos: «Así se luchaba contra la piratería en los videojuegos en los años 80 y 90: historia Retro de una batalla eterna»
    • Xataka: «Así era el maravilloso mundo de los sistemas de protección “artesanales” para los videojuegos de antaño»
    • El confidencial : Jaume Esteve, «Camelot Warriors o el primer sistema anticopia en el ‘software’ español»
    • Jot Down : «No queremos que copies esto» (crónica sobre piratería en España y el dispositivo SD1.
    • NeoTeo: «Dial‑A‑Pirate, y otros métodos anticopia en los videojuegos viejos».
    • NeoTeo: «La protección anticopia de los videojuegos» (visión histórica general).
    • Chicas Gamers : «Sistemas anticopia curiosos de videojuegos» (repaso divulgativo).
    • AcademiaLab: «Lentelok (Lenslok)» (explicación en español del sistema con listado de juegos).

    En inglés

    • WikipediaLenslok (historia, calibración, lista de títulos).
    • CPCWikiSpeedlock (CPC): variantes, velocidades de carga, listado de juegos.
    • iFixit : «Five Weird Ways Old Games Tried to Prevent Copying» (tarjetas de colores, Jet Set Willy, Lenslok, etc.).
     

lunes, 17 de febrero de 2025

Cables de Video para Commodore VIC-20, C64, C16, C128

Ampliado el  18/02/25 en color azul
 
El Commodore VIC-20 disponía de un conector de 5 pines para la salida de video. Los primeros Commodore 16 lo usaron igualmente, pero en el C-64 solo lo tuvieron las primeras unidades, luego se amplió a un conector de 8 pines en U (cuidado que hay otro conector de 8 pines que van en círculo y no se puede usar). Este conector está presente también en el C128, que dispone de otro con señales de color. Como las señales de los primeros 5 pines eran casi las mismas, y solo usamos unas pocas en nuestros cables, y además la posición física de los pines del DIN-5 coinciden con las inferiores del DIN-8 y un conector DIN-5 se puede enchufar en un DIN-8, por lo que se puede usar un cable de 5 pines en cualquier modelo.
 
Los C64 no disponen de salidas RGB en el conector, solo de S-Video y AV, por lo que no se gana nada usando un conector SCART, pero si alguno lo quiere montar porque su tele no tenga AV, que me pase un mensaje y añadiré ese cable en la entrada.
 
Dependiendo del modelo de C64 la señal de +5V del pin 7 pueden faltar, en concreto los modelos C64 y C64A no lo llevan aunque los C64B y C64E si tienen la señal, pero las tres básicas para AV, Sonido y Masa están siempre presentes.

CABLE AV

Pongo los esquemas tanto para conector de 5 como para conector de 8 pines, pero si usáis el de 8 recordar que hay dos modelos de conector, uno con los pines en U y otro con pines en círculo, debéis usar el de pines en U.
 
En el esquema del apartado siguiente para el Euroconector he unido el pin 5 con masa, esto es opcional, según comentan mejora la calidad de salida del Audio al fijar a masa la entrada de sonido.

Cable AV a través del Euroconector

Si alguno no dispone de entradas AV pero si de Euroconector (también llamado Scart), puede usar este cable, no ganará en calidad pues es AV igualmente, solo cambia el cable que usamos. 


Cable S-VIDEO

Es el cable preferido en USA, recordar que no es Super-Video sino Separated-Video (video separado) pues lleva las señales de luna y croma por dos cables separado, mientras que el AV los lleva juntos por uno solo. La diferencia de calidad entre ambos es inapreciable, pues los televisores con S-Video suelen usar un condensador para unir Luma y Croma en su entrada, por lo que al final la señal que procesan es la misma. Cuidado que este sistema no lleva el sonido, solo el Video, por tanto hay que usar otro cable para conectarlo a una fuente externa, por lo que no tiene mucho sentido realmente ni aporta nada sobre el AV.