https://nanochess.org/locs.html
[logo]
Main page
Intel 8080 emulator
Chess programs
Contests
Store
Retrogaming
FAQ
Links
About me
Ver en espanol
LOCS: My drawing language developed at age 9
LOCS in action
The Logo language created by Seymour Papert in 1967 was pretty famous
in the eighties as a tool to teach programming to kids. Now in 2024,
I can see it was great to make nice drawings on the screen and learn
trigonometry, and some very fortunate kids had a real robotic turtle
drawing on paper, but it wasn't so good for teaching programming
because no home computer came with Logo, it was expensive, and its
implementations differed greatly in their support of important
programming constructs.
For example, there is at least one Logo language for Commodore 64
that implemented support for sprites, but it wasn't portable to other
versions. I learned this the hard way as I wanted to play a "Hunt the
Dragon" game made for C64 Logo published in the Mi computer magazine
(the whole volume contains Logo tutorials that I used to learn Logo),
but Dr. Logo just kept throwing me error messages that I didn't
understand.
Anyway, I was in love with Logo, and being the bold kid I was, I
decided to make a Logo language for the student's computer developed
by my father, and I would code it in Z80 machine code at age 9.
Furthermore, I would have forgotten this completely if it wasn't
because the handwritten program was saved away in the class folder
that my father still kept and I found the pages the last Sunday,
March 10, 2024, almost 36 years later. I almost couldn't believe it!
These are pictures of the three pages composing the program. You can
see the machine code is somewhat readable because each instruction is
separated. The final page contains the manual!
LOCS handwritten Z80 machine code, page 1 LOCS handwritten Z80
machine code, page 2 LOCS handwritten Z80 machine code, page 3
I'm glad I put a date on the page: Friday, September 9, 1988. It
means it was written after my first Z80 machine code game, so this is
my second big machine code program.
LOCS means Lenguaje de Oscar Computadora eStudiantil (or Oscar's
language for students' computers). OTEK comes from my father's
initials (Oscar Toledo Esteva) which we used as a kind of company
name.
My language commands are pretty simple: BORRA clears the screen,
TORTUGA centers the "turtle", there are four commands to draw (SM up,
AM down, DM right, IM left), and finally we have the PT command (Pone
Tortuga) to place the turtle anywhere on the screen.
Making it to work
This program depends heavily on having a keyboard, so this time I
won't port it to Colecovision to save me time. I could assign a
function to each key in the keypad but it would require a big code
chunk and I would end up rewriting the program completely.
Anyway, here we go analyzing it. We'll start by preparing a simple
MSX translation layer.
;
; 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
This program calls the ROM of the educative computer. It uses five
functions:
1. L0100, Set VDP address.
2. L0169, Copy a message to VRAM.
3. L04CC, Clear the screen.
4. L04F5, Read the keyboard.
5. RST $10, Read a hexadecimal number into HL (4 digits).
It depends also on the screen grid to be available at the VRAM
address $3c00 (the MSX default is $1800) so in my translation layer
for L04CC it writes the VDP register 2 to change it.
The program starts like this:
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
It sets the stack pointer at the initial address for the 2K RAM
students computer, and immediately the code is patched before showing
the program title.
L8114: CALL L8075
LD HL,$3C01
RET
L8075: LD HL,$3D50
LD (L86F0),HL
CALL L04CC
RET
The patch sets the "turtle" position to $3D50 (the center of the
screen), this position is saved in the L86F0 address, then it clears
the screen calling L04CC, and then loads HL with $3C01 to point to
the top left of the screen. Notice it isn't exactly $3C00 but $3c01
because my Sony Trinitron TV of the time "ate" the first column.
It took me probably half an hour to discover what I patched with tons
of handwritten zero bytes (NOP instructions), and it was simply an
extra message saying "32 BYTES". I think that Dr. Logo showed the
total bytes available for the program, and I think that saying 32
bytes was my joke because there isn't such thing as a memory manager.
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
The main loop starts by calling L81C1, obviously a patch, because it
ends setting HL to $3EC1 (the command area on the screen), but first
it redefines the # character to be a solid block. The redefinition is
done in three areas of VRAM because the educative computer was
hardcoded to VDP mode 2, but the MSX starts in mode 0, so only the
first loop is necessary but I kept it all.
The next call jumps to L807F to draw the "turtle" in the current
position. An asterisk, I'm surprised I didn't even try to draw a
turtle. At the end of the L807F it calls L0100 to point to the VRAM
address for the command area.
Now it shows a "GRAFICO" message (akin to a READY message), and in
the next row ($3EE1 in VRAM) it puts a > sign to wait for the
keyboard. Notice the L8063 patch because I couldn't fit an INC HL to
save the current address in VRAM at L86F2.
There's no cursor, it simply waits for the keyboard and displays the
typed key on the screen (L86F2 contains the current address in VRAM),
and saves the key also in the RAM buffer located at L8700. I've moved
early the CP $0D comparison waiting for the Enter key, so it doesn't
show trash in MSX, because in the educative computer that character
wasn't defined so when displayed it was invisible.
It doesn't handle the backspace key, so you have to think before
typing anything.
On your command
At this point, it starts processing the language commands:
L8068:
LD A,(L8700)
CP $42
JR NZ,L808F
CALL L04CC
JP L8028
The first implemented command is "BORRA", but at the time I didn't
know how to compare whole words, so it simply checks for the first
letter "B" (ASCII $42), calls L04CC and jumps back to 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
The second command is "TORTUGA" (T). It simply gets the current
position of the turtle, puts a space character to erase it, centers
it again on the screen, and draws it again. Apparently I forgot I had
a turtle drawing subroutine at L807F, or that the L8028 jump already
draws the turtle.
L80BF:
CP $53
JR NZ,L80E6
LD A,(L8700+3)
SUB $30
LD B,A
L80C9:
LD HL,(L86F0)
CALL L0100
LD A,$23
OUT (VDP),A
LD DE,$0020
SBC HL,DE
LD (L86F0),HL
DJNZ L80C9
CALL L80B0
CALL L807F
JP L8028
The third command is "SM" (S). It expects the length in the fourth
byte as a digit, so you need to type exactly SM followed by a space
and the digit.
It converts the digit to a value between 0-9 and saves it into B as
counter. It takes the current position of the turtle and draws a
block, displaces the pointer, and repeats. It ends clearing the
command area, redrawing the turtle, and jumping back to the main
loop.
Notice it never validates the length, zero will draw 256 blocks.
Did I say block? Right, I didn't know anything about pixels, so when
you don't have pixels, you have blocks, right? A very bold kid.
L80E6:
CP $44
JR NZ,L811B
LD A,(L8700+3)
SUB $30
LD B,A
L80F0:
LD HL,(L86F0)
CALL L0100
LD A,$23
OUT (VDP),A
LD DE,$0001
ADD HL,DE
NOP
LD (L86F0),HL
DJNZ L80F0
CALL L80B0
CALL L807F
JP L8028
The fourth command is "DM" (D). It is exactly the same code, I only
changed the displacement offset to go the right.
L811B:
CP $41
JR NZ,L8142
LD A,(L8700+3)
SUB $30
LD B,A
L8125:
LD HL,(L86F0)
CALL L0100
LD A,$23
OUT (VDP),A
LD DE,$0020
ADD HL,DE
NOP
LD (L86F0),HL
DJNZ L8125
CALL L80B0
CALL L807F
JP L8028
L8142:
CP $49
JR NZ,L8169
LD A,(L8700+3)
SUB $30
LD B,A
L814C:
LD HL,(L86F0)
CALL L0100
LD A,$23
OUT (VDP),A
LD DE,$0001
SBC HL,DE
LD (L86F0),HL
DJNZ L814C
CALL L80B0
CALL L807F
JP L8028
The fifth command is "AM" (A) for going down, and the sixth command
"IM" (I) for going to left. Again, I only changed the displacement
offsets. I could have saved tons of bytes by having a generic drawing
subroutine where you could provide an offset, but I was still
learning, and probably afraid of juggling with registers.
L8169:
CP $50
JR NZ,L8194-1
LD HL,$3EE5
LD (L87F0),HL
CALL READ_WORD ; Modified from RST $10
LD D,H
LD E,L
LD HL,(L86F0)
CALL L0100
LD A,$20
OUT (VDP),A
LD HL,$3C00
ADD HL,DE
LD (L86F0),HL
CALL L0100
LD A,$2A
OUT (VDP),A
CALL L80B0
JP L8028
The seventh command is "PT". It sets a position for the internal
cursor by reading a hexadecimal word into HL that is saved right away
into DE. It deletes the turtle from the screen, and repositions it at
the DE position added $3c00 to put it into the right VRAM address.
Almost the same code as the TORTUGA command, and again I forgot I had
a turtle redraw subroutine.
By the way, there is a tiny bug here. In the machine code I
calculated wrongly the relative jump JR NZ and it jumps one byte
before the right address, as the opcode $81 is an ADD instruction, it
doesn't affect and it works just right ($41 in 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
Finally, my LOCS program ends with an error message for an
unrecognized command. It does a big delay, erases it, and returns to
the main loop.
I've made a tiny video showing my LOCS program in action.
Downloads
You can download the original LOCS program (495 bytes), and the MSX
adapted version (589 bytes with the translation layer), along with
the original assembler code for both.
* Download locs.zip (4.33 kb) containing assembler source code and
binaries.
You can assemble it with my all-new assembler Gasm80 (available from
https://github.com/nanochess/gasm80) or with TNIasm v0.45.
Epilogue
To be a complete language it would need variables, expressions, and
at least a conditional loop. But doesn't matter, HTML is called a
language and doesn't have this!
I didn't understand exactly how angles worked at the time so there
are none at all in my program. The use of blocks instead of pixels
was crazy, also the lack of validation of the user's typing, but I
can understand I was pretty enthusiastic (detecting commands with one
single letter!) and I empirically discovered the principle of "it is
better to have it working than having nothing".
Over the years I kept working on compilers and interpreters for
several languages going from BASIC to Pascal and then C, maybe later
I'll talk about this. But it wasn't until 2014 that I made again my
own language: IntyBASIC, a dialect of BASIC with dedicated statements
for Intellivision consoles, and more recently CVBasic.
Related links
* My first game coded in Z80 assembly language.
* Mention of Viboritas in Hackaday!
* Cubos: Another of my early machine code games, written in 1991
when I was age 12.
* IntyBASIC is my BASIC language compiler for Intellivision.
* CVBasic is my BASIC language compiler for Colecovision/SG1000/
MSX.
* The modern version of the course still being taught by my father.
Last modified: Mar/12/2024