LOCS: Mi lenguaje de dibujo desarrollado a los 9 años.
El lenguaje Logo creado por Seymour Papert in 1967 fue bastante famoso en los ochentas como una herramienta para enseñar programación a los niños. Ahora en 2024, ya me doy cuenta que era fabuloso para hacer bonitos dibujos en la pantalla y aprender trigonometría, y algunos niños muy afortunados tuvieron una tortuga robótica real dibujando en papel, pero no era muy bueno para aprender programación porque pocas computadora caseras venían con Logo, era caro, y las implementaciones diferían mucho en su apoyo para construcciones de programación importantes. (KingOfCoders me informa que las computadoras Amstrad CPC464 (1984) venían con Dr. Logo ¡qué bien!)
Por ejemplo, existe una implementación de Logo para Commodore 64 que tiene soporte para sprites, pero no es portable a otras versiones. Lo aprendí de la manera difícil cuando quería probar el juego de Caza del Dragón para Logo C64 publicando en la revista Mi computer magazine (el volumen completo contiene tutoriales de Logo que usé para aprender Logo), pero Dr. Logo solo sacaba mensajes de error que yo no comprendía.
De cualquier forma, estaba fascinado con Logo, y siendo un niño audaz decidí hacer un lenguaje Logo para la computadora de estudiantes diseñada por mi padre, y lo haría en código máquina Z80 a los 9 años. De hecho, habría olvidado esto por completo si no fuera por el programa escrito a mano que se guardó en el folder de clases que mi padre todavía conserva y encontré las páginas este domingo 10 de marzo de 2024, casi 36 años después. ¡No podía creerlo!
Estas son fotos de las tres páginas que componen el programa ¡y la página final contiene el manual! Puedes ver que el código máquina es algo legible porque cada instrucción esta separada. Eesto significa que escribí el programa directamente en la computadora educativa y después copié a mano el listado de código máquina en mi cuaderno.
Es afortunado que puse una fecha en la página: Viernes, 9 de septiembre de 1988. Significa que fue escrito después de mi primer juego en código máquina Z80, así que es mi segundo programa grande en código máquina.
LOCS significa Lenguaje de Oscar Computadora eStudiantil. OTEK viene de las iniciales de mi padre (Oscar Toledo Esteva) que usábamos como un tipo de nombre de compañía.
Las órdenes de mi lenguaje eran bastante simples: BORRA limpia la pantalla, TORTUGA centra la "tortuga", hay cuatro órdenes para dibujar (SM arriba, AM abajo, DM derecha, IM izquierda), y finalmente tenemos la orden PT (Pone Tortuga) para poner la tortuga en cualquier posición de la pantalla.
Haciéndolo funcionar
Este programa depende completamente de tener un teclado, así que esta vez no lo portare a Colecovision para ahorrar tiempo. Podría asignar una función a cada tecla del keypad pero requeriría un enorme trozo de código y terminaría reescribiendo el programa completamente.
De cualquier forma, aquí vamos a analizarlo. Comenzaremos preparando una capa simple de traslación para MSX.
;
; LOCS
;
; by Oscar Toledo G.
; (c) Copyright 1988 Oscar Toledo G.
;
; Creation date: Sep/09/1988. I was age 9.
;
;
; Layer to adapt it to MSX computers.
;
WRTVDP: EQU $0047 ; Set VDP address HL.
WRTVRM: EQU $004D ; Write byte A to VRAM address HL.
FILVRM: EQU $0056 ; Fill VRAM address HL with A repeated BC times.
SETWRT: EQU $0053 ; Set write address HL for VDP.
CHGET: EQU $009F ; Read character from keyboard into A.
VDP: EQU $98 ; Base VDP port.
; MSX Cartridge header
ORG $4000
DB "AB"
DW START
DW 0
DW 0
DW 0
DW 0
;
; Set a VDP address to write.
;
L0100: JP SETWRT
;
; Copy immediate data to VRAM.
;
L0169: EX (SP),HL
.1: LD A,(HL)
OR A
JR Z,.2
OUT (VDP),a
INC HL
JR .1
.2: EX (SP),HL
RET
;
; Clear the screen.
; Set screen buffer to VRAM $3c00.
;
L04CC:
LD BC,$0F02 ; VDP register 2 = $3c00.
CALL WRTVDP
LD HL,$3C00 ; Clear the screen.
LD BC,$0300 ; 32x24 grid.
LD A,$20 ; Space character.
JP FILVRM ; Fill VRAM.
;
; Read the keyboard.
;
L04F5:
CALL CHGET ; Read keyboard.
CP $61 ; Is it lowercase?
RET C
CP $7B
RET NC ; No, return.
SUB $20 ; Yes, make it uppercase.
RET
;
; Read a hexadecimal number in HL.
;
READ_WORD:
CALL READ_BYTE ; Read a byte.
READ_BYTE:
CALL READ_HEX ; Read a hexadecimal digit.
READ_HEX:
CALL L04F5 ; Read the keyboard.
PUSH HL
LD HL,(L87F0) ; Get cursor address.
CALL WRTVRM ; Display key on the screen.
INC HL
LD (L87F0),HL ; Update cursor address.
POP HL
SUB $30 ; Convert ASCII 0-9 to 0-9.
CP $0A
JR C,.1
SUB $07 ; Convert ASCII A-F to 10-15.
.1: ADD HL,HL ; Shift HL to the left 4 bits.
ADD HL,HL
ADD HL,HL
ADD HL,HL
OR L ; Add hexadecimal digit.
LD L,A
RET
Este programa llama la ROM de la computadora educativa. Son cinco funciones:
L0100, pone dirección del VDP.
L0169, copia un mensaje a VRAM.
L04CC, limpia la pantalla.
L04F5, lee el teclado.
RST $10, lee un número hexadecimal en HL (4 dígitos).
También depende de que la rejila de la pantalla esté disponible en la dirección VRAM $3c00 (en MSX esto es $1800 por defecto) así que mi capa de traslación para L04CC escribe el registro 2 del VDP para cambiarlo.
El programa empieza así:
START:
LD SP,L87F0
CALL L8114
CALL L0100
CALL L0169
DB "LOCS OTEK",0
IF 0
LD HL,$3C41
CALL L0100
CALL L0169
DB "32 BYTES",0
ELSE
DB 0,0,0
DB 0,0,0
DB 0,0,0
DB 0,0,0,0
DB 0,0,0,0
DB 0
ENDIF
Pone el apuntador de pila a la dirección inicial para la computadora estudiantil de 2K de RAM, e inmediatamente el código es parcheado antes de mostrar el título del programa.
L8114: CALL L8075
LD HL,$3C01
RET
L8075: LD HL,$3D50
LD (L86F0),HL
CALL L04CC
RET
El parche pone la "tortuga" en la posición $3d50 (el centro de la pantala), esta posición se salva en la dirección L86F0, entonces limpia la pantalla llamando L04CC, y carga HL con $3c01 para apuntar a la parte superior izquierda de la pantalla. Nota que no es $3c00 sino $3c01 ya que mi TV Sony Trinitron de la época se "comía" la primera columna.
Probablemente me tomó media hora descubrir lo que parchee con toneladas de bytes zero sobreescritos en la hoja (instrucciones NOP), y era simplemente un mensaje diciendo "32 BYTES". Creo que Dr. Logo mostraba el total de bytes disponibles para el programa, y creo que decir 32 bytes era mi broma debido a que no había tal cosa como gestión de memoria.
L8028:
CALL L81C1
CALL L807F
CALL L0169
DB "GRAFICO",0
LD HL,$3EE1
CALL L8063
LD A,$3E
OUT (VDP),A
LD (L86F2),HL
LD DE,L8700
L8049:
CALL L04F5
LD (DE),A
INC DE
CP $0D
JR Z,L8068
LD HL,(L86F2)
PUSH AF
CALL L0100
POP AF
OUT (VDP),A
INC HL
LD (L86F2),HL
JP L8049
L8063:
CALL L0100
INC HL
RET
L807F:
PUSH HL
LD HL,(L86F0)
CALL L0100
LD A,$2A
OUT (VDP),A
POP HL
CALL L0100
RET
L81C1:
LD HL,$0118 ; Redefine the # character
CALL L0100 ; as a solid block.
LD B,$08
L81C9:
LD A,$FF
OUT (VDP),A
DJNZ L81C9
L81CF:
LD HL,$0918
CALL L0100
LD B,$08
L81D7:
LD A,$FF
OUT (VDP),A
DJNZ L81D7
LD HL,$1118
CALL L0100
LD B,$08
L81E5:
LD A,$FF
OUT (VDP),A
DJNZ L81E5
LD HL,$3EC1 ; Point to command area.
RET
El bucle principal comienza por llamar L81C1, que es obviamente un parche porque termina poniendo HL a $3EC1 (el área de órdenes en la pantalla), pero primero redefine el caracter # para ser un bloque sólido. La redefinición se hace en las tres áreas del VRAM porque la computadora educativa estaba fija en el modo 2 del VDP, pero el MSX comienza en el modo 0, así que solo el primer bucle es necesario pero mantuve todo.
La siguiente llamada salta a L807F para dibujar la "tortuga" en la posición actual. Un asterisco. Me sorprende que ni siquiera traté de dibujar una tortuga. Al final de L807F llama a L0100 para apuntar a la dirección de VRAM del área de órdenes.
Ahora muestra el mensaje "GRAFICO" y en la siguiente línea ($3ee1 en VRAM) pone un signo > para esperar el teclado. Note el parche L8063 porque no me cupo INC HL para salvar la dirección actual de VRAM en L86F2.
No hay cursor, solo espera el teclado e ilustra la tecla entrada en la pantalla (L86F2 contiene la dirección actual en VRAM), y salva la tecla también en el buffer de RAM localizado en L8700. Moví antes la comparación CP $0D que espera por la tecla Enter, para que no muestre basura en MSX, ya que en la computadora educativa ese caracter no estaba definido así que ilustrado era invisible.
No maneja la tecla de retroceso, así que hay que pensar todo antes de teclear.
A sus órdenes
En este punto comienza a procesar las órdenes del lenguaje:
La primera orden implementada es "BORRA", pero en ese momento no sabía como comparar palabras completas, así que solamente checa la primera letra "B" (ASCII $42), llama a L04CC y salta atrás a L8028.
L808F:
CP $54
JR NZ,L80BF
LD HL,(L86F0)
CALL L0100
LD A,$20
OUT (VDP),A
LD HL,$3D50
LD (L86F0),HL
CALL L0100
LD A,$2A
OUT (VDP),A
CALL L80B0
JP L8028
L80B0:
LD HL,$3EC1
L80B3:
CALL L0100
LD B,$40
L80B8:
LD A,$20
OUT (VDP),A
DJNZ L80B8
RET
La segunda orden es "TORTUGA" (T). Sólo obtiene la posición actual de la tortuga, pone un caracter de espacio para borrarla, la centra de nuevo en la pantalla, y la dibuja de nuevo (aparentemente olvidé que tenía la subrutina de dibujar la tortuga en L807F), o que L8028 ya dibuja la tortuga.
La tercera orden es "SM" (S). Espera el tamaño en el cuarto byte como un dígito, así que necesita teclearse exactamente SM seguido por un espacio y luego un dígito.
Convierte el dígito a un valor entre 0 y 9 y lo guarda en B como contador. Toma la posición actual de la tortuga y dibuja un bloque, desplaza el apuntador, y repite. Termina limpiando el área de órdenes, redibujando la tortuga y saltando al bucle principal.
Nota que nunca válida el tamaño, cero dibujará 256 bloques.
¿Dije "bloque"? Correcto, yo no sabía nada acerca de pixeles, así que si no tienes pixeles, usas bloques ¿correcto? De verdad era muy audaz.
La quinta orden es "AM" (A) para ir abajo, y la sexta orden "IM" (I) para ir a la izquierda. De nuevo, solo cambié los offsets de desplazamiento. Pude ahorrar toneladas de bytes con una subrutina genérica de dibujo a la que se provee un offset, pero todavía esta aprendiendo, y probablemente aterrado de manipular tantos registros.
La séptima orden es "PT", pone la posición para el cursor interno leyendo una palabra hexadecimal en HL y la copia en DE. Borra la tortuga de la pantalla, y la reposiciona en la posición de DE más $3c00 para ponerla en la posición correcta de la VRAM. Casi el mismo código de la orden TORTUGA, y de nuevo olvidé la subrutina de redibujar la tortuga.
Por cierto hay un pequeño bug aquí. En el código máquina calculé mal el salto relativo JR NZ y salta un byte antes de la dirección correcta, como el código $81 es una instrucción ADD no afecta y funciona bien ($41 en MSX, LD B,C).
L8194:
LD HL,$3C01
CALL L0100
CALL L0169
DB "* ERROR *",0
LD B,$05
L81A9:
PUSH BC
LD BC,$0000
L81AD:
DEC BC
LD A,B
OR C
JR NZ,L81AD
POP BC
DJNZ L81A9
LD HL,$3C00
CALL L80B3
CALL L80B0
JP L8028
Finalmente, mi programa LOCS termina con un mensaje de error para una orden no reconocida. Hace un gran retardo, borra el mensaje, y vuelve al bucle principal.
Hice un pequeño video mostrando mi programa LOCS en acción.
Descargas
Puedes descargar el programa LOCS original (495 bytes) y la versión adaptada a MSX (589 bytes con capa de traslación), junto con el código ensamblador para ambos.
Descarga locs.zip (4.33 kb) conteniendo código fuente ensamblador y binarios.
Para ser un lenguaje complete necesitaría variables, expresiones, y al menos un bucle condicional. Pero no importa ¡HTML es llamado un lenguaje y no tiene esto!
Yo no entendía exactamente como funcionaban los ángulos así que que no hay ninguno en mi programa. El uso de bloques en lugar de pixeles fue bastante atrevido, así como la falta de validación de la entrada del usuario, pero puedo comprender que estaba muy entusiasta (¡detectar órdenes con una sola letra!) y empíricamente descubrí el principio de "mejor tenerlo funcionando que no tener nada".
A través de los años seguí trabajando en compiladores e intérpretes para varios lenguajes yendo de BASIC a Pascal y luego a C, tal vez después escriba sobre esto. Pero no fue sino hasta 2014 que hice de nuevo mi propio lenguaje: IntyBASIC, un dialecto de BASIC con sentencias dedicadas para consolas Intellivision, y más reciente CVBasic.
Ligas relacionadas
bootLogo, un lenguaje Logo que hice en 509 bytes de código máquina x86 (sector de arranque).