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