Viboritas recreation in 2K made in 1990

I had utterly forgotten about this recreation, however luckily I made backups of my floppy disks for historic report. Not too long ago, I regarded on the floppy disk backup index, and I discovered issues as previous as 1989, and a reputation caught my consideration: Viboritas (Spanish for little snakes).
This recreation was coded round 1990, printed, and later saved onto a 5 1/4″ floppy disk, then copied once more onto a 3 1/2″ floppy disk by 1992 when these turned mainstream (the picture I discovered), and at last, it was again up round 2011. These backups survived by luck a number of onerous drive crashes until we attain 2024.
I opened the file, extracted the 2K binary, and reminiscences began coming slowly. I even caught a 34 year-old bug.
The historical past
A monitor program was a small software within the ROM of the pc the place you had some fundamental instructions like reminiscence itemizing, writing to reminiscence, and executing your program. Some extras have been exit factors in your program to indicate the contents of the Z80 registers and flags.
My growth setting for this recreation was a homebrew laptop for college students with a keyboard, a TV for show, and a set of sheets from Zilog with the Z80 mnemonics and respective machine code.
The specs for this homebrew laptop have been a Zilog Z80 CPU, 2K of EPROM, 2K of RAM, a TMS9118 VDP, and a Commodore keyboard. You would plug in an growth board with an AY-3-8910 sound chip that fed from the Z80 clock. The VDP chip, AY chip, and keyboard have been available due to the 1984 crash.

College students working within the homebrew laptop. You may see a younger @nanochess within the entrance proper. Dec/1990.
Into the binary
For this mission, I needed to do a science fiction recreation in 2K, the place the participant would use ladders, keep away from enemies, and… I had no extra. However I used to be impressed by the ton of ladders in Future Knight. I did not know what measurement coding was, however I put the 2K restrict in my thoughts, and the opposite goal was that the sport must be coded straight into the coed’s laptop, now most often called coding in the true {hardware}.
I did not envision a ultimate for the sport, nor a historical past, or perhaps a gameplay. In spite of everything, I solely needed to have enjoyable creating video games, and naturally, showcase my recreation to the scholars. Discover that having enjoyable creating video games is not the identical as creating enjoyable video games.
I feel that that is my third recreation in machine code for Z80 processors. The binary got here from a 1992 demo disk for college students, however it was initially coded in 1990. I solely extracted the 2K of the sport from the 720K disk picture.
The binary of the sport seems to be like this:

Given the lack of know-how about it, I must reverse-engineer my very own recreation! The primary cross was to completely disassemble it. It sized as much as simply over one thousand strains of Z80 assembler code however extra simply learn trying like this:
ORG $8000 FNAME "viboritas.bin" L04CC: EQU $04CC L0100: EQU $0100 L0169: EQU $0169 L0447: EQU $0447 L8000: CALL L04CC CALL L801B CALL L81BA L8009: CALL L8217 L800C: CALL L8324 CALL L8504 JR NC,L800C L8014: LD HL,L87FC INC (HL) JP L8009
I separated the ROM calls, and I do not keep in mind if I’ve a replica of this laptop ROM, however I nonetheless keep in mind the operate of every name (these are the identical as in my first Z80 recreation). L0100 units a VDP handle (for VRAM or VDP registers), L0169 reads forward to ship knowledge to VRAM, L04CC cleans the display screen, and L0447 reads the keyboard. A factor that modified between 1988 and 1990 computer systems was the port numbers for the VDP.
I made positive the disassembled itemizing assembles the identical binary as the unique. Then I began individually the difference for the MSX computer systems and Colecovision consoles, each having the identical video processor, so you possibly can play the sport. Simply do not anticipate an excessive amount of from a child.
First steps
; ; Viboritas (little snakes) ; ; by Oscar Toledo G. ; (c) Copyright Oscar Toledo G. 1990-2024 ; https://nanochess.org/ ; ; Creation date: Oct/1990. I used to be 11 years previous. ; Revision date: Jan/31/2024. Disassembled. ; Revision date: Feb/01/2024. Ported to MSX/Colecovision. ; COLECO: EQU 1 ; Outline this to 0 for MSX, 1 for Colecovision RAM_BASE: EQU $E000-$7000*COLECO VDP: EQU $98+$26*COLECO PSG: EQU $FF ; Colecovision PSG_ADDR: EQU $A0 ; MSX PSG_DATA: EQU $A1 ; MSX KEYSEL: EQU $80 JOYSEL: EQU $C0 JOY1: EQU $FC JOY2: EQU $FF if COLECO fname "viboritas_cv.ROM" org $8000,$9fff dw $aa55 ; No BIOS title display 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 "viboritas_msx.ROM" org $4000,$5fff dw $4241 dw START dw 0 dw 0 dw 0 dw 0 dw 0 dw 0 WRTPSG: equ $0093 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 name SETWRT pop af out (VDP),a ret FILVRM: push af name SETWRT .1: pop af out (VDP),a push af dec bc ld a,b or c jp nz,.1 pop af ret ; Setup VDP earlier than recreation setup_vdp: LD BC,$0200 CALL WRTVDP LD BC,$C201 ; No interrupts CALL WRTVDP LD BC,$0F02 ; $3C00 for sample desk CALL WRTVDP LD BC,$FF03 ; $2000 for colour desk CALL WRTVDP LD BC,$0304 ; $0000 for bitmap desk CALL WRTVDP LD BC,$3605 ; $1b00 for sprite attribute desk 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 name $00d8 or a ret nz ld a,1 name $00d8 or a ret nz ld a,2 name $00d8 or a ret nz ld a,3 name $00d8 or a ret nz ld a,4 name $00d8 ret endif ; ; Will get the joystick path ; 0 - No motion ; 1 - Up ; 2 - Up + proper ; 3 - Proper ; 4 - Proper + 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 name $00d5 or a ret nz ld a,1 name $00d5 or a ret nz ld a,2 jp $00d5 endif ; ROM routines I forgot ; Clear display screen L04CC: ; $04cc LD HL,$3C00 LD BC,$0300 XOR A JP FILVRM ; Choose handle or register in VDP L0100: LD A,L OUT (VDP+1),A LD A,H ADD A,$40 OUT (VDP+1),A RET ; Copy string to VDP L0169: ; $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 ; ; Begin of the sport ; START: ; 8000 DI ; We do not want interruptions. LD SP,L87F0 if COLECO CALL $1FD6 ; Flip off sound. endif CALL setup_vdp ; Not in unique however wanted to setup VDP. ld hl,$7513 ld (L8780),hl ld hl,$983f ld (L8782),hl ld hl,$c9bf ld (L8784),hl
We now begin analyzing the code, making an attempt to find the way it works. We’ll go ahead and backward on the 2K reminiscence map and as I coded straight in machine code, there aren’t any names for labels besides for his or her corresponding handle within the unique binary.
L8000: CALL L04CC ; Clear the display screen. CALL L801B CALL L81BA L8009: CALL L8217 L800C: CALL L8324 CALL L8504 JR NC,L800C L8014: LD HL,L87FC INC (HL) JP L8009
The primary name is fairly apparent, it merely clears the display screen. Discover the unique recreation assumed the VDP was already initialized, however we already took care of it with CALL setup_vdp.
The following name L801B apparently does graphics arrange.
L801B: LD HL,$0400 ; VRAM bitmap knowledge $80 character. LD DE,L806A LD BC,$00C8 CALL L805D LD HL,$2400 ; VRAM colour knowledge $80 character (1st). LD DE,L8112 LD BC,$001B CALL L8148 LD HL,$2C00 ; VRAM colour knowledge $80 character (2nd). LD DE,L8112 LD BC,$001B CALL L8148 LD HL,$3400 ; VRAM colour knowledge $80 character (third). LD DE,L8112 LD BC,$001B CALL L8148 LD HL,$3800 ; VRAM sprite bitmaps. LD DE,L8404 LD BC,$0100 CALL L805D LD HL,$4400 ; Clearly a patch. JP L83FB L83FB: CALL L0100 LD HL,$41C2 JP L0100
The goal handle for VRAM is within the register HL, whereas the supply handle is in DE, and the byte depend is in BC. That is utterly reversed from the usual Z80 definitions for LDIR, or the MSX LDIRVM BIOS subroutine.
As I used to be coding in machine code, any mistake was a ache to right, particularly in case you wanted to insert extra directions! As you possibly can see the soar to $83FB continues setting register 4 of VDP to zero, after which proceeds to arrange register 1 of VDP for 16×16 sprites.
The VDP in high-resolution mode must have separate bitmap definitions for 3 64-pixel-high areas for a complete of 192 vertical rows. Setting the VDP register 4 is a trick for the VDP to repeat the highest bitmap into the opposite two display screen areas. I discovered this trick months in the past experimenting completely different values for VDP registers.
So we mark L801B as “Setup graphics”.
Then we have now the L805D subroutine that merely copies knowledge from the reminiscence to VRAM.
L805D: CALL L0100 L8060: LD A,(DE) OUT (VDP),A INC DE DEC BC LD A,B OR C JR NZ,L8060 RET
Now I can see why I did it that method because the L0100 subroutine makes use of HL as VDP handle then it was simpler for me to have the supply knowledge pointed by the register DE. I’ve changed the unique port $B0 (VDP write) and $c0 (VDP learn) with the VDP definition for the present console (MSX or Colecovision).
It’s adopted by 200 bytes of bitmaps for the sport:
L806A: db $FF,$FF,$FF,$FF ; $806A db $FF,$FF,$FF,$FF ; $806E db $E7,$E7,$E7,$E7 ; $8072 db $E7,$E7,$E7,$E7 ; $8076 db $FF,$FF,$00,$FF ; $807A db $FF,$00,$FF,$FF ; $807E db $42,$42,$7E,$42 ; $8082 db $42,$7E,$42,$42 ; $8086 db $FE,$82,$BA,$AA ; $808A db $BA,$82,$FE,$00 ; $808E db $BA,$BA,$BA,$BA ; $8092 db $BA,$BA,$BA,$BA ; $8096 db $EE,$00,$FF,$FF ; $809A db $FF,$00,$00,$00 ; $809E db $42,$42,$7E,$42 ; $80A2 db $42,$7E,$42,$42 ; $80A6 db $EF,$EF,$EF,$00 ; $80AA db $FE,$FE,$FE,$00 ; $80AE db $7E,$7E,$7E,$00 ; $80B2 db $6E,$6E,$6E,$00 ; $80B6 db $00,$FF,$FF,$AA ; $80BA db $44,$00,$00,$00 ; $80BE db $42,$42,$7E,$42 ; $80C2 db $42,$7E,$42,$42 ; $80C6 db $EE,$EE,$EE,$00 ; $80CA db $EE,$EE,$EE,$00 ; $80CE db $40,$30,$0C,$03 ; $80D2 db $0C,$30,$40,$40 ; $80D6 db $00,$FF,$00,$AA ; $80DA db $55,$00,$FF,$00 ; $80DE db $81,$81,$C3,$BD ; $80E2 db $81,$81,$C3,$BD ; $80E6 db $81,$58,$37,$47 ; $80EA db $39,$27,$49,$27 ; $80EE db $47,$49,$27,$40 ; $80F2 db $28,$15,$12,$27 ; $80F6 db $00,$FE,$FE,$00 ; $80FA db $EF,$EF,$00,$00 ; $80FE db $0C,$0C,$18,$18 ; $8102 db $30,$30,$18,$18 ; $8106 db $54,$FE,$54,$FE ; $810A db $54,$FE,$54,$00 ; $810E
This consists of partitions, columns, ground, and ladders (4 characters for every degree), for a complete of 5 ranges, plus a sort of drain cowl. Subsequent is the colour desk for these bitmaps.
L8112: db $08,$22,$08,$3C ; $8112 db $08,$A1,$08,$F1 ; $8116 db $08,$74,$08,$E1 ; $811A db $01,$F1,$01,$11 ; $811E db $03,$E1,$03,$11 ; $8122 db $08,$F1,$08,$6E ; $8126 db $08,$E1,$10,$F1 ; $812A db $08,$61,$08,$A1 ; $812E db $03,$F1,$02,$51 ; $8132 db $03,$F1,$08,$E1 ; $8136 db $08,$98,$08,$32 ; $813A db $01,$11,$06,$6E ; $813E db $01,$11,$08,$31 ; $8142 db $08,$F1 ; $8146
What is that this? This knowledge can’t be copied on to the VDP, as an alternative it seems to be like there are byte counts.
L8148: CALL L0100 LD B,C L814C: PUSH BC LD A,(DE) LD B,A INC DE LD A,(DE) INC DE L8152: OUT (VDP),A NOP DJNZ L8152 POP BC DJNZ L814C RET
And that is proper, the child was sensible sufficient to create a decompressor that reads a depend of bytes and a byte to copy. So 73 bytes substitute 200 bytes of colour.

Bitmaps used for the extent backgrounds alongside character quantity. Discover the order of wall, column, ground, and ladder.
Nonetheless, for this text I am going to assist my youthful myself designing all-new graphics. If you’re interested in it you possibly can see the earlier graphics set within the viboritas_orig.asm file.

On the left you possibly can see the 1990 sprites and on the proper the up to date sprites.

The brand new sprite set for Viboritas.
; ; Sprites for the participant and half of the snakes. ; L8404: ; $00 - Participant going proper (body 1). DB $00,$01,$05,$03,$07,$03,$07,$1e DB $37,$67,$77,$74,$03,$0e,$0e,$0f DB $00,$50,$f0,$f0,$d0,$70,$10,$e0 DB $00,$b8,$b8,$00,$c0,$f8,$7c,$00 ; $04 - Participant going proper (body 2). DB $00,$02,$01,$03,$01,$03,$03,$07 DB $07,$06,$06,$07,$03,$03,$03,$03 DB $a8,$f8,$f8,$e8,$b8,$88,$70,$80 DB $c0,$e0,$e0,$00,$c0,$00,$c0,$e0 ; $08 - Participant going left (body 1). DB $00,$0a,$0f,$0f,$0b,$0e,$08,$07 DB $00,$1d,$1d,$00,$03,$07,$1e,$3e DB $00,$80,$a0,$c0,$e0,$c0,$e0,$78 DB $ec,$e6,$ee,$2e,$c0,$70,$70,$f0 ; $0c - Participant going proper (body 2). DB $15,$1f,$1f,$17,$1d,$11,$0e,$01 DB $03,$07,$07,$00,$03,$00,$03,$07 DB $00,$40,$80,$c0,$80,$c0,$c0,$e0 DB $e0,$60,$60,$e0,$c0,$c0,$c0,$c0 ; $10 - Participant utilizing ladder (body 1). DB $0a,$07,$0f,$0f,$07,$07,$03,$0c DB $1b,$70,$73,$02,$06,$06,$1e,$3e DB $a0,$c0,$e0,$e0,$ce,$ce,$98,$70 DB $c0,$00,$c0,$60,$38,$3c,$00,$00 ; $14 - Participant utilizing ladder (body 2). DB $05,$03,$07,$07,$73,$73,$19,$0e DB $03,$00,$03,$06,$1c,$3c,$00,$00 DB $50,$e0,$f0,$f0,$e0,$e0,$c0,$30 DB $d8,$0e,$ce,$40,$60,$60,$78,$7c ; $18 - Snake going left (body 1). DB $1b,$2nd,$2nd,$36,$1f,$7d,$9b,$03 DB $0f,$1f,$3e,$3c,$3c,$3f,$1f,$0f DB $00,$00,$00,$00,$00,$80,$80,$82 DB $02,$06,$06,$0e,$cc,$ec,$fc,$38 ; $1c - Snake going proper (body 2). DB $00,$0d,$16,$16,$1b,$0f,$1e,$5d DB $61,$0f,$1f,$1e,$1e,$1f,$0f,$07 DB $00,$80,$80,$80,$00,$80,$c0,$c0 DB $c0,$84,$0c,$cc,$d8,$f8,$b8,$30
Music participant
At this second of the evaluation, the next routine is not known as but.
L815B: LD HL,L87FA INC (HL) LD A,(HL) CP $08 JR NZ,L8194 LD (HL),$00 DEC HL INC (HL) LD A,(HL) CP $30 JR NZ,L816F LD (HL),$01 L816F: LD A,(HL) ADD A,255 AND (L8744-1) LD L,A LD H,(L8744-1)>>8 CALL L8197 NOP LD A,(HL) OUT ($80),A INC HL LD A,$01 OUT ($00),A LD A,(HL) OUT ($80),A LD A,$07 OUT ($00),A LD A,$B8 OUT ($80),A LD A,$08 OUT ($00),A LD A,$0A OUT ($80),A L8194: JP L8398 L8197: LD A,(HL) ADD A,A ADD A,255 AND (L81A2-2) LD L,A LD H,(L81A2-2)>>8 XOR A OUT ($00),A RET L81A2: dw $01ac dw $0153 dw $011d dw $00fe dw $00f0 dw $0140 dw $00d6 dw $00be dw $00b4 dw $00aa dw $00a0 dw $00e2 L8744: db $01,$02,$03,$04 ; $8744 db $05,$04,$03,$02 ; $8748 db $01,$02,$03,$04 ; $874C db $05,$04,$03,$02 ; $8750 db $06,$04,$07,$08 ; $8754 db $09,$08,$07,$04 ; $8758 db $06,$04,$07,$08 ; $875C db $09,$08,$07,$04 ; $8760 db $03,$0C,$08,$0A ; $8764 db $0B,$0A,$08,$0C ; $8768 db $06,$04,$07,$08 ; $876C db $09,$08,$07,$04 ; $8770
Okay, it increments a byte at L87FA, and when it reaches the worth 8 it’s reset to zero and proceeds to increment L87F9 till it reaches 48 when it’s reset to 1. In fact! L87F9 is the index quantity within the tune desk, and L87FA is the counter of observe length.
Then it makes use of the index to get the observe to play from L8744. Here’s a machine code trick that is not helpful when changing to assembler mnemonics: You recognize the information is mounted on the handle, so there isn’t any dealing with for carry to the upper handle byte.
Now we have now one other patch, this time calling L8197 to get the observe frequency to play, after which it writes to the AY-3-8910 sound chip. The $00 port handle units the AY-3-8910 index register, and the $80 port handle units the AY-3-8910 knowledge register. It even units the register 7 of PSG to $38 to disable white noise, and this worth can burn for actual some MSX1 computer systems. Allow us to substitute the sound code:
CALL L8197 if COLECO LD A,(HL) INC HL LD H,(HL) LD L,A AND $0F OR $80 OUT (PSG),A SRL H RR L SRL H RR L SRL H RR L SRL H RR L LD A,L OUT (PSG),A LD A,$93 OUT (PSG),A else LD E,(HL) LD A,0 CALL WRTPSG INC HL LD E,(HL) LD A,1 CALL WRTPSG LD E,$0A LD A,$08 CALL WRTPSG endif L8194: JP L8398 L8197: LD A,(HL) ADD A,A ADD A,255 AND (L81A2-2) LD L,A LD H,(L81A2-2)>>8 RET
Additionally at first look, I assumed this routine was written first, however then I spotted it occupies the house of an uncompressed colour desk! Once I optimized the colour desk definition to make use of compression, I added the tune participant within the freed house!
This implies the primary model of my recreation did not have any music, and there is a probability that someplace in my information exists a printout. And I simply remembered a factor: I coded the sport with out music, and a scholar had a background in music, and he handed me some musical notes that I applied terribly as a result of I did not know something about music timing.
The music is a rendition of a boogie-woogie. The music participant nonetheless goes on, and there’s a very bizarre patch leaping at L8398. However we’ll see it later, because it seems to be just like the keyboard code.
Initialization
Now we go to the subsequent unexplored code at L81BA:
L81BA: XOR A LD (L87F3),A LD (L87F4),A LD (L87F7),A LD (L87F8),A LD A,$F0 LD (L87F5),A LD A,$01 LD (L87F6),A LD A,$0F LD (L8786),A LD A,$00 LD (L8787),A LD HL,$0000 LD (L87F9),HL LD A,$02 LD (L87FB),A CALL L04CC LD HL,$3EE9 CALL L0100 CALL L0169 db "(C) OTEK 1990",0 LD HL,$3EAC CALL L0100 CALL L0169 db "VIDAS:0",0 LD A,$01 JP L82A5
That is trying like an initialization code (allow us to add a observe to L81BA as recreation initialization).
We’ll uncover the operate of every variable quickly. Thus far it cleans once more the display screen, units up the VDP to the final row of the display screen, and reveals the copyright message. OTEK comes from my father’s initials (Oscar Toledo Esteva) which we used as a sort of firm title.
Additionally, it reveals the variety of remaining lives (VIDAS in Spanish), and… one other patch jumps to L82A5.
L82A5: LD (L87FC),A XOR A LD (L87FE),A LD (L87FF),A RET
It’s merely some extra variable initialization.
Display screen drawing
Now one other routine L8217 that known as instantly after in L8009:
L8217: LD A,(L87FC) ADD A,A ADD A,A ADD A,$7C LD (L87FD),A LD HL,$3C00 CALL L0100 LD B,$A0 L8229: PUSH BC LD B,$03 L822C: LD A,(L87FD) OUT (VDP),A INC HL DJNZ L822C LD A,(L87FD) INC A OUT (VDP),A INC HL POP BC DJNZ L8229 LD HL,$3C80 LD B,$04 L8243: PUSH BC PUSH HL CALL L0100 LD B,$20 L824A: LD A,(L87FD) ADD A,$02 OUT (VDP),A INC HL DJNZ L824A POP HL LD BC,$00A0 ADD HL,BC POP BC DJNZ L8243 LD HL,$4701 CALL L0100 LD HL,$2000 CALL L8277 LD HL,$2800 CALL L8277 LD HL,$3000 CALL L8277 JP L8288
It begins by getting a variable from L87FC, multiplying by 4, including $7c, and saving the end result at L87FD. L87FC is initialized to 1. This begins at $80, it sounds just like the character quantity for degree definition. It attracts in sequence 160 partitions (3 characters) + columns (one character), then it attracts over 4 flooring beginning at row 4 ($3c80), each 32 characters vast, utilizing the character in L87FD offset by 2.
It units the border to black ($4701), after which the bottom charset ($00-$7f characters) for the three zones of the display screen is about to black (three calls to L8277)
L8277: LD A,H ADD A,$04 LD B,A CALL L0100 L827E: LD A,$F1 OUT (VDP),A INC HL LD A,H CP B JR NZ,L827E RET L8288: LD HL,$3E5E CALL L0100 LD A,$94 OUT (VDP),A LD HL,$3C80 CALL L82B0 LD HL,$3D20 CALL L82B0 LD HL,$3DC0 CALL L82B0 RET
The L8288 patch provides the “drain” character $94 on the backside proper of the display screen. I nonetheless do not perceive why I did not simply draw a 2×2 door, however most likely I felt like there have been house constraints (defining the graphics and drawing the tiles).
On the finish, it calls 3 instances the L82B0 subroutine with completely different display screen rows as the bottom:
L82B0: LD A,(L87FC) LD B,A LD A,$06 SUB B LD B,A L82B8: PUSH BC PUSH HL NOP LD D,$00 NOP CALL L82CB LD E,A ADD HL,DE CALL L830F POP HL POP BC DJNZ L82B8 RET
This L82B0 subroutine subtracts the extent quantity from 6 and calls a subroutine L82CB to get an offset, after which L830F to do one thing.
L82CB: PUSH BC PUSH DE PUSH HL LD HL,(L8780) LD DE,(L8782) LD BC,(L8784) ADD HL,HL ADD HL,HL ADD HL,BC ADD HL,DE LD (L8780),HL ADD HL,DE ADD HL,DE ADD HL,BC ADD HL,BC ADD HL,BC ADD HL,HL ADD HL,HL ADD HL,HL ADD HL,HL ADD HL,DE ADD HL,BC LD (L8782),HL ADD HL,DE ADD HL,DE ADD HL,BC ADD HL,HL ADD HL,DE ADD HL,BC ADD HL,BC ADD HL,BC ADD HL,BC LD (L8784),HL LD HL,(L8780) LD DE,(L8782) LD BC,(L8784) ADD HL,DE ADD HL,BC LD A,H ADD A,L AND $1F POP HL POP DE POP BC RET
It occurs L82CB seems to be so much like a random quantity generator, and solely will get a quantity between 0 and 31 within the accumulator register.
L830F: LD B,$05 LD A,(L87FD) ADD A,$03 L8316: PUSH AF CALL L0100 POP AF OUT (VDP),A LD DE,$0020 ADD HL,DE DJNZ L8316 RET
¡Thriller solved! L830F attracts a ladder on the display screen. Every ladder is 5 rows excessive, and it’s drawn utilizing the extent base character offset by 3. Within the first degree it would draw 5 ladders on every ground, whereas within the fifth degree, it would draw just one ladder for every ground. That is an try and make harder ranges.
With all this code analyzed we will mark safely L8217 as the extent drawing code.
The hero and the foes
Now we have now the primary routine known as from the principle loop and it’s L8324:
L8324: LD HL,$1B00 LD A,(L8786) CALL L8390 INC HL LD A,(L8787) CALL L8390 INC HL LD A,(L87FE) CALL L8390 INC HL LD A,$0F CALL L8390 INC HL LD A,$38 CALL L8390 INC HL LD DE,L87F3 CALL L86C5 LD A,$0E CALL L8390 INC HL LD A,$60 CALL L8390 INC HL LD DE,L87F5 CALL L86C5 LD A,$0E CALL L8390 INC HL LD A,$88 CALL L8390 INC HL LD DE,L87F7 CALL L86C5 LD A,$0E CALL L8390 JP L815B L8390: PUSH AF CALL L0100 POP AF OUT (VDP),A RET
It hundreds HL with $1b00, pointing to the Sprite Attribute Desk. The VRAM place the place sprites are positioned on the display screen. And it begins studying variables and writing to VRAM utilizing L8390 (fairly just like WRTVRM of MSX)
L8786 is the Y-coordinate for the participant, L8787 is the X-coordinate for the participant, L87FE is the sprite body for the participant. The participant colour is white.
It’s simply deduced subsequent that the enemies are positioned at mounted vertical positions on the display screen ($38, $60, and $88) utilizing a generic routine L86C5. Discover additionally that it chain hyperlinks to the L815B subroutine for enjoying the background music, which in flip chain hyperlinks to the keyboard decode subroutine (L8398).
L86C5: LD A,(DE) CALL L8390 INC DE INC HL LD A,(DE) LD B,$18 CP $00 JR NZ,L86D4 LD B,$20 L86D4: LD A,(L8788) XOR $01 LD (L8788),A BIT 0,A LD A,$00 JR Z,L86E4 LD A,$04 L86E4: ADD A,B CALL L8390 INC HL PUSH HL LD HL,$390C LD DE,L8704 LD BC,$0034 CALL L805D POP HL RET
The primary byte pointed by DE is used for the X-coordinate of the enemy. And the subsequent byte alerts the motion path to pick the sprite body for the enemy. It additionally switches frames utilizing L8788 to get alternate motion frames (together with B set to base body $18 or $20). Then it does one thing actually bizarre, copying the reminiscence space L8704 into VRAM handle $390c. Oh, I see, it defines two sprites very late within the recreation (the 2 sprite frames for snakes shifting to the suitable), it’s sort of apparent that I did not foresee all of the required sprites for the sport.
For this up to date model of the sport, I am going to modify barely the code:
LD HL,$3900 ; Outline body sprites $20 and $24 LD DE,L8700 ; Knowledge for snake going to proper. LD BC,$0040 ; Size of information. CALL L805D ; Copy to VRAM.
It could not have match by 8 bytes, or I must transfer a good portion of code to create space. On this case my possibility may have been shifting the portion of code at $8504-$8533 (the advanced enemy motion code), however I did not have a MOVE command within the monitor program. I needed to copy manually the machine code on the new place.
; ; Additional sprites for snakes going proper. ; L8700: DB $00,$01,$01,$01,$00,$01,$03,$03 DB $03,$21,$30,$33,$1b,$1f,$1d,$0c DB $00,$b0,$68,$68,$d8,$f0,$78,$ba DB $86,$f0,$f8,$78,$78,$f8,$f0,$e0 DB $00,$00,$00,$00,$00,$01,$01,$41 DB $40,$60,$60,$70,$33,$37,$3f,$1c DB $d8,$b4,$b4,$6c,$f8,$be,$d9,$c0 DB $f0,$f8,$7c,$3c,$3c,$fc,$f8,$f0
Participant motion
The music participant chain hyperlinks to the keyboard code:
L8398: CALL L0447 CP $10 JP Z,L83DD CP $0F JP Z,L83B5 CP $37 JP Z,L85B5 CP $38 JP Z,L8567 CP $02 JP Z,L8684 RET
Now this code is trying each second extra like spaghetti code, and it additionally must be rewritten for moveable joystick dealing with:
L8398: CALL GTSTCK CP $07 ; Going left? JP Z,L83DD CP $03 ; Going proper? JP Z,L83B5 CP $01 ; Going up? JP Z,L85B5 CP $05 ; Taking place? JP Z,L8567 CALL GTTRIG CP $ff ; Button pressed? JP Z,L8684 RET
The primary joystick subroutine is L83B5:
L83B5: LD HL,L8787 INC (HL) INC (HL) NOP LD A,(HL) CP $00 JR NZ,L83C2 LD (HL),$FE L83C2: LD A,(L87FE) CP $04 JR NZ,L83CD LD A,$00 JR L83CF L83CD: LD A,$04 L83CF: LD (L87FE),A RET
At this level, we all know that L8787 is the X-coordinate of the participant (based mostly on the Sprite Attribute Desk writes), and the double increment makes clear that the participant strikes to the suitable. If the X-coordinate turns into 0, it’s rewritten with restrict $fe (254 pixels). It additionally animates the participant switching between sprite frames $00 and $04.
L83DD: LD HL,L8787 DEC (HL) DEC (HL) NOP LD A,(HL) CP $FE JR NZ,L83EA LD (HL),$00 L83EA: LD A,(L87FE) CP $0C JR NZ,L83F5 LD A,$08 JR L83F7 L83F5: LD A,$0C L83F7: LD (L87FE),A RET
The following subroutine is the other: Transferring the participant to the left by two pixels. It additionally checks for exceeding the left border and units the X-coordinate to zero. It animates the participant switching between sprite frames $08 and $0c.
The NOP instruction after the 2 decrements makes me assume that I thought of shifting horizontally the participant at a velocity of three pixels.
Utilizing ladders
The code to permit the participant to go up and down over the ladders is closely patched, so most likely it took me quite a lot of effort and experiments.
Allow us to begin with the code to go down (this was coded first as a result of the participant must go down from the highest ground):
; ; Transfer the participant downward. ; L8567: LD HL,$1B00 CALL L85A0 CALL L85AE NOP LD D,A INC HL CALL L85A0 CALL L85FF RRCA RRCA LD E,A LD A,D LD L,A LD H,$00 ADD HL,HL ADD HL,HL ADD HL,HL ADD HL,HL ADD HL,HL LD D,$00 ADD HL,DE LD DE,$3C40 ADD HL,DE CALL L85A0 LD B,A LD A,(L87FD) ADD A,$03 CP B RET NZ CALL L85E9 ADD A,$02 L859C: LD (L8786),A RET L85A0: LD A,L OUT (VDP+1),A LD A,H OUT (VDP+1),A NOP NOP NOP NOP NOP IN A,(VDP) RET L85AE: INC A AND $F8 RRCA RRCA RRCA RET L85E9: LD D,$00 ADD HL,DE LD A,(L87FE) LD B,$14 CP $10 JR Z,L85F7 LD B,$10 L85F7: LD A,B LD (L87FE),A LD A,(L8786) RET L85FF: ADD A,$04 AND $F8 RRCA RET L8605: ADD A,$03 CP B RET Z POP HL LD A,(L8786) INC A AND $F8 JP L8631 L8631: DEC A NOP LD (L8786),A RET
The harmless child reads from VRAM the coordinates of the participant, however why on Earth? These variables have been already obtainable in RAM.
It first reads from VRAM $1b00 the Y-coordinate of the participant into register D transformed to a display screen row coordinate, and it additionally reads the X-coordinate into register E and adjusts it to a display screen column coordinate. It lastly takes each numbers to create a pointer to the VRAM display screen.
D = (Y + 1) / 8 E = (X + 4) / 8 HL = D * 32 + E + $3c20
You may see I did LD A,D adopted by LD L,A once I may merely do LD L,D.
It reads the character from VRAM (CALL L85A0), and it checks if the character is a ladder (the contents of L87FD plus 3). It calls a patch L85E9 that for some purpose provides a price to the content material of HL, animates the participant utilizing the ladder (sprite frames $10 and $14), and will get the Y-coordinate of the participant to maneuver it two pixels downward.
; ; Transfer the participant upward. ; L85B5: LD A,(L8786) CALL L85AE LD D,A INC HL LD A,(L8787) CALL L85FF RRCA RRCA LD E,A LD A,D LD L,A LD H,$00 ADD HL,HL ADD HL,HL ADD HL,HL ADD HL,HL ADD HL,HL LD D,$00 ADD HL,DE LD DE,$3C20 ADD HL,DE CALL L85A0 LD B,A LD A,(L87FD) CALL L8605 NOP CALL L85E9 SUB $02 JP L859C
The code for shifting the participant upwards is fairly comparable, and by some means I did the suitable factor on this code utilizing the prevailing coordinates in RAM. This implies I used to be reaching my limits, and having 2K of machine code within the head is not really easy!
There are a couple of variations extra just like the completely different offset on the display screen ($3c20 versus $3c40), and the actual fact it calls L8605 to do a comparability with the ladder character. If it is not a ladder, it aligns the participant vertically (once more utilizing a patch), and utilizing POP HL it returns to the principle loop as an alternative of the unique caller. If it’s a ladder, it strikes the participant two pixels upwards.
Now for the good embarrassing second: the participant can stroll over the air. As a result of the code for dealing with left and proper by no means checks if the participant is over a ground. Because the flooring are at all times in the identical vertical place, it might be merely a matter of checking if the participant is over one of many legitimate Y-coordinates, however I can keep in mind vaguely I used to be afraid of shifting the code once more. Lazy child!
Profitable the sport
As soon as the participant reaches the grid within the bottom-right of the display screen, the hearth button must be pressed to cross the extent. I watched in delight as college students forgot to press the button they usually have been caught by the snake.
; ; Button press to exit degree. ; L8684: LD A,(L8786) CP $87 RET NZ LD A,(L8787) CP $E8 RET C CP $F8 RET NC LD SP,L87F0 LD A,$0F LD (L8786),A XOR A LD (L8787),A LD A,(L87FC) CP $05 JP NZ,L8014 LD HL,$3D4A CALL L0100 CALL L0169 db "HAS GANADO !",0 LD A,$08 OUT ($00),A XOR A JP L87B4 L87B4: OUT ($80),A JP L878A L878A: LD B,$05 ; Massive delay. L878C: PUSH BC LD BC,$0000 L8790: DEC BC LD A,B OR C JR NZ,L8790 POP BC DJNZ L878C LD SP,L87F0 ; Reset Stack Pointer. LD A,$0F ; Reset Y-coordinate for the participant. LD (L8786),A XOR A ; Reset X-coordinate for the participant. LD (L8787),A LD A,$01 ; Restart at degree 1. LD (L87FC),A LD HL,L87F9 LD (HL),$00 INC HL LD (HL),$00 JP L8009
The subroutine first checks for the Y-coordinate to be $87, and the X-coordinate is between $e8 and $f7 (good tolerance) and if the circumstances are met it resets the stack pointer, units the participant once more on the top-left of the display screen, and if the extent quantity is not 5 then it jumps to L8014 to extend the extent quantity else it reveals a message “HAS GANADO” (Spanish for you win) on the display screen.
It additionally turns off the music in one other tender instance of chain linking due to the closely patched code.
The sound code must be rewritten as this:
db "HAS GANADO !",0 if COLECO ld a,$9f out (PSG),a else ld e,$00 ld a,$08 name WRTPSG endif JP L87B4 L87B4: JP L878A
The L878A routine does a giant delay so the “HAS GANADO !” message stays on the display screen, after which resets the sport, and sends the participant again to degree 1.
Enemy motion
The second subroutine known as by the principle loop of the sport is L8504, it reveals heavy patches.
L8504: CALL L850A JP L83D3
It calls L850A after which L83D3. L83D3 is extra akin to a loop to make the sport run slower (I nonetheless did not know the VDP interrupt line, nor did I’ve the road related to the Z80 processor). After setting BC to $1000, it additionally updates the present variety of lives on the display screen.
L83D3: CALL L861E L83D6: DEC BC LD A,B OR C JR NZ,L83D6 AND A RET L861E: LD BC,$1000 LD A,(L87FB) ADD A,$30 LD HL,$3EB2 PUSH AF CALL L0100 POP AF OUT (VDP),A RET
The subroutine L850A is longer:
L850A: LD HL,L87F3 CALL L8517 LD L,255 AND L87F5 CALL L8517 LD L,255 AND L87F7 L8517: INC HL LD A,(HL) OR A LD B,$03 JR Z,L8520 LD B,$FD L8520: DEC HL LD A,(HL) ADD A,B LD (HL),A CP $FF JR NZ,L852D INC HL LD (HL),$01 JR L8533 L852D: OR A JR NZ,L8533 INC HL LD (HL),$00 L8533: CALL L855B CP B RET C CP C RET NC LD A,L SUB 255 AND L87F3 RRCA ADD A,A ADD A,A ADD A,$04 LD L,A LD H,$1B LD A,L OUT (VDP+1),A LD A,H OUT (VDP+1),A NOP NOP NOP NOP IN A,(VDP) LD B,A LD A,(L8786) INC A CP B RET NZ JP L8613 L855B: DEC HL LD A,L AND $FE OR $01 LD L,A LD B,(HL) JP L8738 L8738: LD A,(L8787) LD C,B DEC B DEC B DEC B INC C INC C INC C INC C RET
It makes use of L8517 every time with a pointer to one of many enemies (L87F3, L87F5, and L87F7). For every enemy, it checks the present path and selects an X-displacement (-3 or +3 pixels) within the B register. If it reaches a sure coordinate it switches the motion path.
As soon as this has been executed, one other patch calls to L855B to make HL level precisely to the X-coordinate of the enemy (this code depends on the reminiscence handle of the enemy coordinates). It reads the present X-coordinate into the B register and jumps to a different patch in L8738, the place it reads the X-coordinate of the participant in A, makes a replica of B in C, subtracting 3 from B, and provides 4 to C.
When it has created a collision width (minimal is B, most is C) it does a comparability of A (participant X-coordinate) with B and returns whether it is lower than, and a comparability in opposition to C and returns whether it is equal or higher than.
Because the enemy state does not comprise its Y-coordinate, it determines the sprite from the enemy knowledge handle, reads the Sprite Attribute Desk from VRAM to get the Y-coordinate and does a comparability with the participant Y-coordinate (L8786), and returns if each aren’t equal, else it jumps to L8613 to kill the participant.
There’s a bug on this code and the participant can die by chance whereas strolling. In a tremendous instance of how bugs can perdure for years, I could not discover this unintentional kill bug for years till right this moment (Feb/06/2024) I lastly used debuging instruments of BlueMSX. It’s fairly simple as soon as discovered, when a snake is aligned with the participant it returns appropriately as a result of the participant is not in the identical ground because the snake, however it loses the worth of the register HL as a result of the VRAM learn, and the subsequent snake X-coordinate might be learn from ROM creating a hard and fast invisible snake within the subsequent ground. It can fail randomly able dependant of the platform. Do you wish to right it? Simply substitute my “sensible” optimization in L850A to load every time the complete worth of HL with the handle of the enemy knowledge as an alternative of solely the L register. Case closed, it solely took me 34 years.
Let’s proceed:
L8613: CALL L837A DEC (HL) SCF LD SP,L87F0 JP L8637 L837A: XOR A OUT ($00),A LD A,$AE OUT ($80),A LD A,$01 OUT ($00),A LD A,$06 OUT ($80),A JP L8774 L8774: LD BC,$0000 L8777: DEC BC LD A,B OR C JR NZ,L8777 JP L866F L866F: LD A,$08 OUT ($00),A XOR A OUT ($80),A LD BC,$0000 L8679: DEC BC LD A,B OR C JR NZ,L8679 LD HL,L87FB JR L86F8 L86F8: XOR A LD (L87F9),A LD (L87FA),A RET L8637: LD A,$0F LD (L8786),A XOR A LD (L8787),A LD A,(L87FB) CP $FF JP NZ,L8009 LD HL,$3D4A CALL L0100 CALL L0169 db "FIN DE JUEGO",0 LD B,$05 L8660: PUSH BC LD BC,$0000 L8664: DEC BC LD A,B OR C JR NZ,L8664 POP BC DJNZ L8660 JP L8000
It’s fairly embarrassing this chain-linking of code, however allow us to go in components.
The primary line of code at L8613 calls L837A, the final word objective is loading HL with L87FB to level to the variety of lives of the participant and decrement it.
However L837A additionally creates a sound impact (a primary!) then jumps to L8774 for a small delay, after which jumps to L866F to show off the amount, does one other delay, hundreds HL with a pointer to the variety of lives, and resets the music participant’s variables.
After it decrements the variety of lives, it units the carry flag however clearly, I obtained misplaced on this path as a result of it’s by no means used. The stack pointer is reset, the participant is about once more to the beginning level in L8637, and if there are nonetheless lives it jumps to L8009 to proceed the sport, or else it shows a message “FIN DE JUEGO” (recreation over in Spanish), it waits an extended time, and it resets utterly the sport leaping to L8000.
We have to patch the L837A and L866F sound routines with this:
L837A: if COLECO ld a,$8E out (PSG),a ld a,$2a out (PSG),a else ld e,$ae ld a,$00 name WRTPSG ld e,$06 ld a,$01 name WRTPSG endif JP L8774 L866F: if COLECO ld a,$9f out (PSG),a else ld e,$00 ld a,$08 name WRTPSG endif LD BC,$0000 L8679: DEC BC
The used variables
The ultimate checklist of variables contained in the code are:
L8780: rb 2 ; Random generator 1. L8782: rb 2 ; Random generator 2. L8784: rb 2 ; Random generator 3. L8786: rb 1 ; Y-coordinate for the participant. L8787: rb 1 ; X-coordinate for the participant. L8788: rb 1 ; Animation bit for snakes. L87F3: rb 1 ; X-coordinate of enemy 1. L87F4: rb 1 ; X-direction of enemy 1. L87F5: rb 1 ; X-coordinate of enemy 2. L87F6: rb 1 ; X-direction of enemy 2. L87F7: rb 1 ; X-coordinate of enemy 3. L87F8: rb 1 ; X-direction of enemy 3. L87F9: rb 1 ; Observe index for music participant. L87FA: rb 1 ; Tick counter for music participant. L87FB: rb 1 ; Present lives. L87FC: rb 1 ; Present degree. L87FD: rb 1 ; Base character for drawing ranges. L87FE: rb 1 ; Sprite body for the participant. L87FF: rb 1 ; Not used, but initialized.
The stack pointer was at $87F0 for 2K RAM scholar computer systems in 1990. Later moved to $fff0 for 32K RAM (1992).
You may obtain the ROM for the sport able to be performed on a Colecovision or MSX. I’ve additionally included the commented supply code. The one distinction between this and my 1990 recreation is the redesigned graphics and changes to degree colours to boost visibility. The unique colours blended badly on trendy emulators (in 1990 I may modify distinction within the Sony Trinitron TV).
- Obtain viboritas.zip (17.24 kb). Supply code, ROM for Colecovision and MSX, and unique binary and disassembly.
Epilogue
Quite a lot of bytes may very well be saved on this recreation by refactoring some components like utilizing an additional byte to protect the vertical place of enemies, shifting some initialization code out of the principle loop (lives replace and snake sprite definition), utilizing knowledge obtainable on RAM as an alternative of studying VRAM, and compacting the music participant code.
On the opposite facet, it displays my skills on the time. I may have nearly 2K of machine code on my head, there wasn’t a plan forward (denoted by the ton of patches). I used to be nonetheless studying learn how to code a platform recreation, and I wasn’t too ready to attract graphics.
Writing video games or different code straight in machine code is not sensible. Though at first look you possibly can have the whole lot in your head, you will neglect utterly after a couple of years, and likewise except you could have some paper documentation there are no useful feedback!
I’d have used an assembler program if these have been available, however I had none till I wrote mine some years after. The software program shops in Mexico have been scarce, additionally I by no means may discover one thing so esoteric as a Z80 assembler program when the IBM PC was already the dominant machine.
Nonetheless, my goal of creating a recreation in 2K RAM was met. College students have been shocked an actual recreation may work on their laptop. I feel I distributed a couple of copies as printed sheets with the binary and one other few copies in floppies.
I realized as I developed the sport, and I did not make once more the error of permitting the participant to stroll into the void. However nonetheless for a few years, I saved coding in machine code and doing spaghetti code once I wanted to insert code, however that could be a historical past for one more day.
Associated hyperlinks
Final modified: Feb/08/2024