I was cleaning an old work area when I saw my old blue notebook that I used from 1986 to 1989. It had suffered the pass of time and dust.
My old notebook from 1988
I thought it was the same notebook I had seen before, because I had two blue notebooks of the same model, but I lost one. And then to my surprise, I discovered it was the lost second notebook, where I put my first Z80 assembly language program!
Pretty happy, I took pictures immediately of the important pages, just in case I lose again the notebook.
And memories start coming slowly...
The history
I was 9 years old and I wanted to write amazing games. Of course, I was too young to create something in the league of the games that I was watching in magazines at the time like Compute! and Input MSX.
After four years of writing games in BASIC language, I felt like I was stopped by the slowness inherent to an interpreted language.
This game was written around June 1988, my father was giving a lecture on programming the VDP processor TMS9128. The video processor was pretty available at the time (via Digikey) because the demise of all computers using it.
My notes on the VDP 16K VRAM structure. Notice the register numbers.
The students had been soldering their computers based on Z80 over a copper board perforated with holes spaced by 0.1"
Unfortunately my hands weren't able at the time to handle precisely a soldering iron, so I had been watching awkwardly how the students enjoyed building their computers, how they managed to get them running, and how they troubleshooted their bugs.
This gave me plenty of time to think about the Z80 assembler programming concept, I had been unable to grasp the concept of how to put together the instructions. The previous weeks before that Sunday (I can remember it was Sunday), while watching my father putting together the monitor/VDP/Keyboard EPROM code for the students computer, finally I had the imaginary light bulb over my head. Of course! Several instructions compose a BASIC language statement, so it was a matter of doing the same but in assembler.
The Sunday morning before class, I started writing furiously over my notebook a game about a karateka fighting a woman launching knifes. I had the whole idea on my head, now I realize for the first time I had the BASIC algorithm clearly in my mind. It was only a matter of translating it to assembler. I had realized that the line separation of BASIC was invisible on assembler, but each address could be related to that imaginary line number in BASIC language.
You can imagine the surprise of the whole class when I showed my game proudly. I remember three students copying manually the code from the RAM dump and then making Xerox copies for the other students.
Getting this to work in 2021
Here are the pictures of my notebook. You'll need to forgive my terrible kid letters. Barely any comments but anything you see will be in Spanish, because that is my first language.
While commenting each page (and analyzing my novice 9 years old myself!), I'll translate this game to tniASM v0.44 for both MSX and Colecovision (just because). We need the following translation and support layer because we don't have at hand the original ROM from the students computer:
;
; Karateka
;
; by Oscar Toledo G.
; (c) Copyright Oscar Toledo G. 1988-2021
; https://nanochess.org/
;
; Creation date: Jun/1988. I was 9 years old.
; Revision date: May/12/2021. Ported to MSX/Colecovision.
;
COLECO: EQU 0 ; Define this to 0 for MSX, 1 for Colecovision
RAM_BASE: EQU $E000-$7000*COLECO
VDP: EQU $98+$26*COLECO
KEYSEL: EQU $80
JOYSEL: EQU $C0
JOY1: EQU $FC
JOY2: EQU $FF
if COLECO
fname "KARATECV.ROM"
org $8000,$9fff
dw $aa55 ; No BIOS title screen
dw 0
dw 0
dw 0
dw 0
dw START
jp 0 ; RST $08
jp 0 ; RST $10
jp 0 ; RST $18
jp 0 ; RST $20
jp 0 ; RST $28
jp 0 ; RST $30
jp 0 ; RST $38
jp 0 ; No NMI handler
else
fname "KARATMSX.ROM"
org $4000,$5fff
dw $4241
dw START
dw 0
dw 0
dw 0
dw 0
dw 0
dw 0
SNSMAT: equ $0141
endif
WRTVDP:
ld a,b
out (VDP+1),a
ld a,c
or $80
out (VDP+1),a
ret
SETWRT:
ld a,l
out (VDP+1),a
ld a,h
or $40
out (VDP+1),a
ret
WRTVRM:
push af
call SETWRT
pop af
out (VDP),a
ret
FILVRM:
push af
call SETWRT
.1: pop af
out (VDP),a
push af
dec bc
ld a,b
or c
jp nz,.1
pop af
ret
; Setup VDP before game
setup_vdp:
LD BC,$0200
CALL WRTVDP
LD BC,$C201 ; No interrupts
CALL WRTVDP
LD BC,$0F02 ; $3C00 for pattern table
CALL WRTVDP
LD BC,$FF03 ; $2000 for color table
CALL WRTVDP
LD BC,$0304 ; $0000 for bitmap table
CALL WRTVDP
LD BC,$3605 ; $1b00 for sprite attribute table
CALL WRTVDP
LD BC,$0706 ; $3800 for sprites bitmaps
CALL WRTVDP
LD BC,$0407 ; Blue border
CALL WRTVDP
IF COLECO
LD HL,($006C) ; MSX BIOS chars
LD DE,-128
ADD HL,DE
ELSE
LD HL,($0004) ; MSX BIOS chars
INC H
ENDIF
PUSH HL
LD DE,$0100
LD BC,$0300
CALL LDIRVM
POP HL
PUSH HL
LD DE,$0900
LD BC,$0300
CALL LDIRVM
POP HL
LD DE,$1100
LD BC,$0300
CALL LDIRVM
LD HL,$2000
LD BC,$1800
LD A,$F4
CALL FILVRM
RET
LDIRVM:
EX DE,HL
.1: LD A,(DE)
CALL WRTVRM
INC DE
INC HL
DEC BC
LD A,B
OR C
JR NZ,.1
RET
GTTRIG:
if COLECO
out (KEYSEL),a
ex (sp),hl
ex (sp),hl
in a,(JOY1)
ld c,a
in a,(JOY2)
and c
ld c,a
out (JOYSEL),a
ex (sp),hl
ex (sp),hl
in a,(JOY1)
and c
ld c,a
in a,(JOY2)
and c
rlca
rlca
ccf
ld a,0
sbc a,a
ret
else
xor a
call $00d8
or a
ret nz
ld a,1
call $00d8
or a
ret nz
ld a,2
call $00d8
or a
ret nz
ld a,3
call $00d8
or a
ret nz
ld a,4
call $00d8
ret
endif
;
; Gets the joystick direction
; 0 - No movement
; 1 - Up
; 2 - Up + right
; 3 - Right
; 4 - Right + down
; 5 - Down
; 6 - Down + left
; 7 - Left
; 8 - Left + Up
;
GTSTCK:
if COLECO
out (JOYSEL),a
ex (sp),hl
ex (sp),hl
in a,(JOY1)
ld b,a
in a,(JOY2)
and b
and $0f
ld c,a
ld b,0
ld hl,joy_map
add hl,bc
ld a,(hl)
ret
joy_map:
db 0,0,0,6,0,0,8,7,0,4,0,5,2,3,1,0
else
xor a
call $00d5
or a
ret nz
ld a,1
call $00d5
or a
ret nz
ld a,2
jp $00d5
endif
; ROM routines I forgot
; Clean screen
LIMPIA: ; $04cc
LD HL,$3C00
LD BC,$0300
XOR A
JP FILVRM
; Copy string to VDP
INMEDIATO: ; $0169
EX (SP),HL
.0: LD A,(HL)
INC HL
OR A
JR Z,.1
PUSH AF
POP AF
OUT (VDP),A
JR .0
.1: EX (SP),HL
RET
;
; Start of the game
;
START: ; 8000
DI
LD SP,RAM_BASE+256
Every extra code will be added at the end of the previous code.
Page 1 of my Z80 game, right-click for higher resolution.
So here is the page 1 of my Z80 game. The source code at right was written thinking in putting it inside an assembler program that I was going to develop (imagine that, a 9 years old ambitiously thinking in writing an assembler!).
To enter this game into the students' computer, one needs to enter only the microcode at the left. If you found an error on the right side source code is because it is mean to be as reference only.
The code basically setups the game and creates a game loop. I misspelled NAVAJA (knife) as NEVAJA. Notice how I replaced PONEHL for a similar MSX routine WRTVDP. The INMEDIATO routine copies the following bytes directly to VDP until finding a zero byte.
JUEGO:
CALL setup_vdp ; Not in original but needed to setup VDP
CALL LIMPIA ; Clean screen
CALL PRESENTACION ; Title screen
CALL GRAFICOS ; Setup graphics
PRIN: ; 8009
CALL PATCH ; Patch for jump
CALL KARATEKA ; Move karateka
CALL MALORA ; Move baddie
CALL NEVAJA ; Move knifes
JP PRIN ; Repeat game loop
; Title screen
PRESENTACION: ; 8018
LD HL,$3C4D
CALL SETWRT
CALL INMEDIATO
Page 2 of my Z80 game.
Of course, I didn't had any idea of using directives except to put a byte at once. This is the title screen. "PULSE ESPACIO" means to press the keyboard space bar. I changed it to any trigger on MSX or Colecovision.
DB "KARATEKA",0
LD HL,$3ECA
CALL SETWRT
CALL INMEDIATO
DB "PULSE ESPACIO",0
Page 3 of my Z80 game.
It uses an internal BIOS routine $04f5 to read the keyboard matrix, and it has been replaced with a MSX GTTRIG call (or Colecovision read of buttons).
Then it setups the graphics for the karateka, the knife, and the judoka.
; Changed from the original
TEC: XOR A
CALL GTTRIG
OR A
RET NZ
JP TEC
; Setup graphics for Karateka, Knife, and Judoka
GRAFICOS:
LD HL,$3800
LD DE,GRAFICOS_8600
LD B,$00
.1: LD A,(DE)
CALL WRTVRM
INC HL
INC DE
DJNZ .1
LD HL,$0B08
LD DE,GRAFICOS_85D8
LD B,$28
.2: LD A,(DE)
Page 4 of my Z80 game.
It now setups some variables for the position of the karateka, and the knifes shoot by the enemy. Also resets the kicks_given variable. It starts filling the color for the sky and grass.
Then it cleans the screen and starts putting the characters on the screen to create visually the sky and grass. And yes, embarrassingly the 9 years old is repeating code like crazy!
For some unclear reason I set again the Register 1 of VDP, then it setups the start position for the player. Notice the microcode says 3E 68 corrected while the source code at right reads LD A,70 putting the player under the floor.
The movement is left and right, up for punchs, and down for kicks. Again the keyboard reading has been replaced for MSX GTSTCK (translated for Colecovision).
I think I was going through a mental compilation of BASIC to assembler, only that could explain why the code is pretty unoptimized. Optimizing it a little would have saved me lots of time re-entering the game into the computer.
Moving the karateka to left was accomplished simply by subtracting 8 to the X coordinate, and updating it also on the sprite attributes table. Also it animates the player between two frames.
CP $05
JP Z,GODPI
RET
; Move karateka to left
MIK: ; 8149
LD A,(x_karateka)
SUB $08
LD (x_karateka),A
LD HL,$1b01
CALL WRTVRM
LD A,(spr_karateka)
CP $00
JP Z,SP2
LD A,$00
LD (spr_karateka),A
JP SPR
SP2: ; 8167
LD A,$08
LD (spr_karateka),A
SPR: LD HL,$1b02
CALL WRTVRM
RET
Page 9 of my Z80 game.
The movement to right replicates essentially the same code for left movement. It could have used the same subroutine by passing parameters on DE or BC registers.
Given it has no idea of the player direction, it checks the sprite frame to see in which direction he/she is facing. Another variable would have made it so much easier.
The code to punch enemy to left (GAI) and right (GAD), it simply updates the sprite to the punch frame, and calls NADEO (where it checks if enemy is hit), then it does a wait (stopping the whole game!) and restores the original sprite. I still needed to grasp the concept of independent objects.
JP Z,GAD
CP $0C
JP Z,GAD
RET
GAI: ; 81B5
LD HL,$1B02
LD A,$10
ZON: CALL NADEO
LD BC,$4000
.1: DEC BC
LD A,B
OR C
JR NZ,.1
LD A,(spr_karateka)
JP WRTVRM
GAD: ; 81CB
LD HL,$1B02
LD A,$14
JP ZON
Page 11 of my Z80 game.
The code to handle kicks is essentially a copy of the previous code. BOLAS is essentially saying Ay Caramba!
GODPI: ; 81D3 Forgot label on code
LD A,(spr_karateka)
CP $00
JP Z,IZ
CP $08
JP Z,IZ
CP $04
JP Z,DER
CP $0C
JP Z,DER
RET
IZ: ; 81EB
LD HL,$1B02
LD A,$18
HOLA: CALL NADEO
LD BC,$4000
BOLAS: DEC BC
LD A,B
OR C
JR NZ,BOLAS
LD A,(spr_karateka)
JP WRTVRM
DER: ; 8201
LD HL,$1B02
LD A,$1C
JP HOLA
Page 12 of my Z80 game.
After watching there was no way of avoiding the knifes, I implemented the jump for the karateka.
This is showing more thinking, like calling NEVAJA to keep the knifes moving. But still two separate routines, one to move player up, and one to move player down. Each with their own delay.
; Karateka jump
SALTO: ; 8209
LD A,(y_karateka)
LD B,$04
OSC: LD HL,$1B00
SUB $04
CALL WRTVRM
PUSH HL
PUSH AF
CALL NEVAJA
PUSH BC
LD BC,$4000
.1: DEC BC
LD A,B
OR C
JR NZ,.1
POP BC
POP AF
POP HL
DJNZ OSC
LD B,$04
KAR: LD HL,$1B00
ADD A,$04
CALL WRTVRM
PUSH HL
PUSH AF
CALL NEVAJA
PUSH BC
Page 13 of my Z80 game.
Moving the knifes is simply a running counter from left to right, updating the screen.
The kid couldn't solve how to make knifes closer to the enemy launching them, because the knifes cleared behind with spaces so these would have erased the right half of the enemy.
LD BC,$4000
BOCHA: DEC BC
LD A,B
OR C
JR NZ,BOCHA
POP BC
POP AF
POP HL
DJNZ KAR
RET
NEVAJA: ; 8247
LD A,(knife1)
ADD A,$A5
LD H,$3D
LD L,A
LD A,$65
CALL WRTVRM
LD A,(knife1)
ADD A,$A4
LD H,$3D
LD L,A
LD A,$60
CALL WRTVRM
LD A,(knife1)
INC A
Page 14 of my Z80 game.
An amazing waste of bytes by repeating the same code to move a parallel knife on the same column by using a different variable.
Again a waste of bytes to delete each knife separately, when both come together. Probably I was thinking on having them appear on different times, but couldn't figure how to do it. The MALORA function (baddy) checks if the knifes hit the player, again checking each knife separately.
LD L,A
LD A,$60
CALL WRTVRM
LD A,$00
LD (knife1),A
RET
B2: DEC A
ADD A,$C5
LD H,$3D
LD L,A
LD A,$60
CALL WRTVRM
LD A,$00
LD (knife2),A
RET
MALORA: ; 82B9
LD A,(x_karateka)
RRCA
RRCA
RRCA
LD B,A
LD A,(knife1)
ADD A,$05
CP B
JP Z,MUERTO
LD A,(knife2)
Page 16 of my Z80 game.
The MUERTO (dead) function simply fills the whole color table with a random colors (Hey! That's a special visual effect!) And stops before resetting to the internal monitor. Couldn't insert a jump to the main game because the RST 00 instruction only uses one byte, and a jump uses 3 bytes (remember I wasn't using an assembler) so for this time I changed it to jump to the game.
ADD A,$05
CP B
JP Z,MUERTO
RET
MUERTO: ; 82D3
LD HL,$2000
LD BC,$1800
M2: LD A,R
CALL WRTVRM
INC HL
DEC BC
LD A,B
OR C
JR NZ,M2
LD BC,$0000
M3: DEC BC
LD A,B
OR C
JR NZ,M3
JP JUEGO ; Not in original because lack of space
Page 17 of my Z80 game.
Finally my first working subroutine NADEO, called 4 times to check for enemy being hit. I cannot figure what I was trying to abbreviate there. The hit check is simple, if the player is standing exactly where the knifes appear, it will hit the enemy, of course I was trying to make him/her to be hit by the knifes. Unfortunately this exact position is hitting on the air, and there is no visual indication of hit...
NADEO: ; 82ED
CALL WRTVRM
PUSH AF
PUSH HL
PUSH BC
PUSH DE
LD A,(x_karateka)
CP $28
CALL Z,GOLPES
POP DE
POP BC
POP HL
POP AF
RET
GOLPES: ; 8301
LD A,(kicks_given)
INC A
LD (kicks_given),A
CP $14
JP Z,GANAR
RET
GANAR: ; 830E
CALL LIMPIA
LD HL,$3C03
CALL SETWRT
CALL INMEDIATO
Page 18 of my Z80 game.
This part is simple. Only a winning message (BIEN! or good!)
DB "BIEN!",0
LD BC,$0000
BION: DEC BC
LD A,B
OR C
JR NZ,BION
JP JUEGO
Page 19 of my Z80 game.
Obviously I wrote the whole game from my brain to paper, and didn't planned for a delay to make the game playable. This code handles it and also the jumping.
Notice how I didn't use interrupts, I didn't knew about interrupts at the time, but the simple idea of having a constant time reference in terms of video frames would have blown my mind!
PATCH:
LD BC,$4000
.1: DEC BC
LD A,B
OR C
JR NZ,.1
XOR A
CALL GTTRIG
OR A
JP NZ,SALTO
RET
Page 20 of my Z80 game.
This page doesn't need to be ported as VPOKE is replaced by the WRTVRM routine. And VPEEK isn't used. By the way, this shows the influence of my Input MSX magazine readings.
Page 21 of my Z80 game.
Page 22 of my Z80 game.
These pages comprise the graphics for the game. There should be squared paper somewhere containing my original drawings but I don't have found them.
GRAFICOS_85D8:
DB $00,$03,$06,$0D,$1B,$17,$03,$01 ; JUDOKA
DB $00,$C0,$00,$C0,$40,$C0,$80,$00
DB $0F,$F3,$00,$07,$07,$02,$1C,$30
DB $F0,$CF,$00,$E0,$E0,$40,$38,$0C
DB $10,$10,$20,$FF,$FF,$20,$10,$10 ; KNIFE
GRAFICOS_8600:
DB $03,$07,$07,$03,$01,$03,$03,$03 ; KARATEKA LEFT
DB $03,$03,$03,$01,$01,$01,$03,$0F
DB $C0,$E0,$E0,$C0,$80,$C0,$C0,$C0
DB $C0,$C0,$C0,$80,$80,$80,$80,$80
DB $03,$07,$07,$03,$01,$03,$03,$03 ; KARATEKA RIGHT
DB $03,$03,$03,$01,$01,$01,$01,$01
DB $C0,$E0,$E0,$C0,$80,$C0,$C0,$C0
DB $C0,$C0,$C0,$80,$80,$80,$C0,$F0
DB $03,$07,$07,$03,$01,$03,$03,$03 ; KARATEKA WALKING LEFT
DB $03,$03,$03,$01,$02,$04,$0C,$3D
DB $C0,$E0,$E0,$C0,$80,$C0,$C0,$C0
DB $C0,$C0,$C0,$80,$40,$20,$60,$E0
DB $03,$07,$07,$03,$01,$03,$03,$03 ; KARATEKA WALKING RIGHT
DB $03,$03,$03,$01,$02,$04,$06,$07
DB $C0,$E0,$E0,$C0,$80,$C0,$C0,$C0
DB $C0,$C0,$C0,$80,$40,$20,$30,$BC
DB $03,$07,$07,$03,$01,$FF,$FF,$03 ; KARATEKA PUNCH LEFT
DB $03,$03,$03,$01,$01,$01,$03,$0F
DB $C0,$E0,$E0,$C0,$80,$C0,$C0,$C0
DB $C0,$C0,$C0,$80,$80,$80,$80,$80
DB $03,$07,$07,$03,$01,$03,$03,$03 ; KARATEKA PUNCH RIGHT
DB $03,$03,$03,$01,$01,$01,$01,$01
DB $C0,$E0,$E0,$C0,$80,$FF,$FF,$C0
DB $C0,$C0,$C0,$80,$80,$80,$C0,$F0
DB $03,$07,$07,$03,$01,$03,$C3,$C3 ; KARATEKA KICK LEFT
DB $33,$33,$0F,$07,$00,$00,$01,$07
DB $C0,$E0,$E0,$C0,$80,$C0,$C0,$C0
DB $C0,$C0,$C0,$80,$80,$80,$80,$80
DB $03,$07,$07,$03,$01,$03,$03,$03 ; KARATEKA KICK RIGHT
DB $03,$03,$03,$01,$01,$01,$01,$01
DB $C0,$E0,$E0,$C0,$80,$C0,$C3,$C3
DB $CC,$CC,$F0,$E0,$00,$00,$80,$E0
Page 23 of my Z80 game.
The last page contains documentation of the variables used and half of it is desynchronized, and the knife 3 wasn't implemented. It's a practical example of real documentation :P