Cubos: Un juego de 3K hecho en 1991

Cubos actualizado en acción
Mientras escribía felizmente mi artículo sobre Viboritas, noté un juego Cubos en el mismo disco pero no podía recordar de que se trataba.
Unos pocos días después cuando agregué los dibujos faltantes para Karateka, me di cuenta de que tenía un dibujo previo completo de la pantalla para Cubos. Y de nuevo, comencé a recordar cosas y corregir bugs de hace más de 30 años.

Televideo

El año era 1986, y mi padre había reconstruido un Televideo PC de partes. Teníamos unos pocos discos flexibles, y uno de los juegos era J-Bird por Greg Kuperberg. Ilustraba un pájaro saltando en una pirámida construida de cubos, y tenías que llenar cada cubo. En niveles avanzados era necesario saltar varias veces sobre el mismo cubo para obtener el color correcto mientras evitabas pelotas y serpientes.
Por supuesto, estoy describiendo Q*Bert, pero en ese momento no lo sabía, y era un concepto nuevo para mí. Me tomó años descubrir que J-Bird era un clon del arcade.
Jugué bastante J-Bird y otros juegos antiguos de PC ese año. Recuerdo estar hipnotizado por la apariencia seudo-3D pero no tenía idea de como lograr ese efecto en mis juegos.
Sin embargo ya había dibujado la siguiente idea para mi juego de salta cubos:
Early draft of Cubos
Dibujo previo de Cubos en mi cuaderno.
Early draft of player sprite for Cubos
Dibujo previo del sprite del jugador para Cubos.
Puedes ver que mi mente de niño no se había dado cuenta aún de que la pirámide podía ser ceñida a los bordes de los cuadros (la rejilla de caracteres en la pantalla), y hacer eso me hubiera despertado a la idea de dibujar la pirámide completa usando caracteres predefinidos. Mi idea era de que el hombre casette luchaba contra los discos malvados y la serpiente sería un joystick.
J-Bird era demasiado para mi conocimiento de esa época, pero la idea perduró por algunos años hasta principios de 1991 cuando tenía 12 años.

El juego

El binario viene de un disco que utilicé mientras trabajaba en una de los computadoras homebrew construidas por mi padre. El disco en si no tenía un sistema operativo, en lugar de eso mostraba un menú para escoger los juegos, y comandaba las subrutinas de disco para traer el track del disco y cargarlo en la direción $8000.
Trabajé directamente en código máquina usando un programa monitor, así que uno de los primeros pasos es desensamblar el juego completo. Para hacerlo funcionar con MSX y Colecovision reusaremos la capa de traslación que hice para mi primer juego Z80.

        ;
        ; Cubos
        ;
        ; by Oscar Toledo G.
        ; https://nanochess.org/
        ;
        ; Creation date: Early 1991. I was age 12.
        ; Revision date: Feb/12/2024. Ported to MSX/Colecovision.
        ;

COLECO: EQU 1   ; Define 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 "cubos_cv.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 "cubos_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

SETRD:
	ld a,l
	out (VDP+1),a
	ld a,h
    and $3f
	out (VDP+1),a
	ret

WRTVRM:
	push af
	call SETWRT
	pop af
	out (VDP),a
	ret

RDVRM:
        push af
        call SETRD
        pop af
        ex (sp),hl
        ex (sp),hl
        in a,(VDP)
        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,$8201     ; No interrupts, disable screen.
	CALL WRTVDP
    LD BC,$0602     ; $1800 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,$0107     ; Black border
	CALL WRTVDP
    LD HL,$0000
    LD BC,$1800
    XOR A
    CALL FILVRM
    LD HL,$2000
    LD BC,$1800
    LD A,$F1
    CALL FILVRM
    LD HL,$1B00
    LD A,$D0
    CALL WRTVRM
    LD HL,$1800
.1:
    LD A,L
    CALL WRTVRM
    INC HL
    LD A,H
    CP $1B
    JP NZ,.1
    LD BC,$C201     ; No interrupts, enable screen
	CALL WRTVDP
	RET

	;
	; 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

	; Select address or register in VDP
L0100:
	LD A,L
	OUT (VDP+1),A
	LD A,H
	ADD A,$40
	OUT (VDP+1),A
	RET

L0309:  
        CALL SETWRT
.1:
        LD A,(DE)
        OUT (VDP),A
        INC DE
        DEC BC
        LD A,B
        OR C
        JP NZ,.1
        RET

L0099:      EQU setup_vdp

L0AE8:  ; ???
        RET

L0109:  ; ???
        RET

L0454:      EQU RDVRM
L0447:      EQU WRTVRM

START:
        LD SP,STACK
El juego desensamblado ocupa 1292 líneas de ensamblador. Cada etiqueta está nombrada de acuerdo a su dirección original en el juego. Necesitamos seguir el código a través de varias etiquetas debido a mi estilo de programación de ese tiempo basado en parches.
El juego utiliza solo 4 bytes de RAM para datos de trabajo, debido a que tomé una terrible decisión de diseño para mantener las coordenadas X,Y del jugador y los enemigos en la VRAM y esto utilizó más código que tenerlas directamente en RAM normal.
Aquí vamos con la primera pieza de código:

L8000:      	CALL L836A
L8003:      	CALL L0099
            	LD HL,$2000
            	CALL L0100
L800C:      	LD A,$F1
            	OUT ($98),A
            	INC HL
            	LD A,H
            	CP $38
            	JR NZ,L800C
            	LD HL,$4701
            	CALL L86E0
            	LD DE,$1080
            	LD A,$05
            	CALL L8027
            	JP L8043

L836A:      	XOR A
            	CALL L83FB
            	JP L8696

L83FB:      	LD (LFF0F),A
            	CALL L85B9
            	RET

L85B9:      	LD (LFF0E),A
            	INC A
            	LD (LFF0D),A
            	RET

L8696:      	CALL L0099
            	JP L0AE8
La llamada a L836A parece inicializar tres variables, dos de ellas a cero, y la otra a uno, entonces termina llamando L0099 y L0AE8, por suerte recuerdo que estas se usaban para poner el modo de video de alta resolución. Bien, al menos L0099, porque no recuerdo la función de L0AE8.
Por alguna razón no observé que L8003 de nuevo llama L0099 para reiniciar el VDP, y procede a limpiar el espacio de color con blanco y negro. Mi amor por el MSX se nota aquí en la puerta $98 para escribir datos en el VDP, pero en realidad, era $90 en la computadora de mi padre, y para este juego voy a reemplazarla con la etiqueta VDP para poder ensamblar este juego tanto para MSX como para Colecovision.
Ahora pone el borde como color negro llamanda L0100 en L86E0.

L86E0:      	CALL L0100
                LD DE,L8700
            	LD B,$12
L86E8:      	LD A,(DE)
            	LD H,A
            	INC DE
            	LD A,(DE)
            	LD L,A
            	INC DE
            	PUSH BC
            	LD B,$08
L86F1:      	LD A,(DE)
            	CALL L0447
            	INC HL
            	INC DE
            	DJNZ L86F1
            	POP BC
            	DJNZ L86E8
            	JP L87B4

L8700:
                db $00,$00,$00  
            	db $00,$00,$00,$03  ; $8703
            	db $03,$0F,$0F,$00  ; $8707
            	db $08,$07,$3F,$FF  ; $870B
            	db $FF,$FF,$FF,$FF  ; $870F
            	db $FF,$00,$10,$C0  ; $8713
            	db $F6,$FF,$F3,$FF  ; $8717
            	db $FC,$FF,$F0,$00  ; $871B
            	db $18,$00,$00,$00  ; $871F
            	db $00,$60,$C0,$D0  ; $8723
            	db $50,$01,$00,$0F  ; $8727
            	db $0F,$0F,$0F,$0F  ; $872B
            	db $0F,$0E,$0E,$01  ; $872F
            	db $08,$FF,$FF,$FF  ; $8733
            	db $FF,$C3,$00,$00  ; $8737
            	db $00,$01,$10,$F5  ; $873B
            	db $FC,$E5,$CD,$FE  ; $873F
            	db $00,$00,$00,$01  ; $8743
            	db $18,$A0,$50,$A0  ; $8747
            	db $60,$50,$60,$70  ; $874B
            	db $60,$02,$00,$0E  ; $874F
            	db $0E,$0E,$0E,$07  ; $8753
            	db $06,$04,$04,$02  ; $8757
            	db $08,$0F,$31,$C1  ; $875B
            	db $3D,$4F,$3D,$01  ; $875F
            	db $01,$02,$10,$0F  ; $8763
            	db $30,$C0,$3C,$4E  ; $8767
            	db $3C,$00,$00,$02  ; $876B
            	db $18,$A0,$A0,$50  ; $876F
            	db $40,$60,$80,$C0  ; $8773
            	db $C0,$03,$00,$03  ; $8777
            	db $03,$03,$03,$03  ; $877B
            	db $01,$01,$01,$03  ; $877F
            	db $08,$02,$0C,$04  ; $8783
            	db $03,$00,$00,$00  ; $8787
            	db $03,$03,$10,$00  ; $878B
            	db $60,$80,$00,$00  ; $878F
            	db $00,$00,$C0,$03  ; $8793
            	db $18,$40,$80,$40  ; $8797
            	db $80,$80,$00,$80  ; $879B
            	db $00,$04,$08,$07  ; $879F
            	db $03,$00,$80,$C0  ; $87A3
            	db $F0,$FE,$FF,$04  ; $87A7
            	db $10,$E0,$C0,$00  ; $87AB
            	db $01,$03,$0F,$7F  ; $87AF
            	db $FF  ; $87B3
Dibuja una serie de 18 caracteres en la pantalla tomando una dirección VRAM y 8 bytes de datos. Decidí conservar la dirección VRAM destino en formato big-endian en lugar del más común little-endian usado en el Z80.
Esto dibuja el bitmap de una cara en la parte superior izquierda de la pantalla. Creo que esto es algo que agregué después de hacer funcionar el juego debido a la dirección de memoria
Bitmap de cara dibujada en Cubos
Bitmap de cara dibujada en Cubos.
Entonces repite el mismo código para dibujar la misma cara en la parte derecha de la pantalla.

L87B4:          LD DE,L87D0	; Pointer to bitmap data for face.
            	LD B,$12	; 18 characters.
L87B9:      	LD A,(DE)	; Read target VRAM address.
            	LD H,A
            	INC DE
            	LD A,(DE)
            	LD L,A
            	INC DE
            	PUSH BC
            	LD B,$08	; 8 bytes.
L87C2:      	LD A,(DE)	; Read byte.
            	CALL L0447	; Copy to VRAM.
            	INC HL
            	INC DE
            	DJNZ L87C2
            	POP BC
            	DJNZ L87B9	; Repeat until all characters are copied.
            	JP L8884

L87D0:          db $00,$E0,$00,$00  ; $87D0
            	db $00,$00,$03,$03  ; $87D4
            	db $0F,$0F,$00,$E8  ; $87D8
            	db $07,$3F,$FF,$FF  ; $87DC
            	db $FF,$FF,$FF,$FF  ; $87E0
            	db $00,$F0,$C0,$F6  ; $87E4
            	db $FF,$F3,$FF,$FC  ; $87E8
            	db $FF,$F0,$00,$F8  ; $87EC
            	db $00,$00,$00,$00  ; $87F0
            	db $60,$C0,$D0,$50  ; $87F4
            	db $01,$E0,$0F,$0F  ; $87F8
            	db $0F,$0F,$0F,$0F  ; $87FC
            	db $0E,$0E,$01,$E8  ; $8800
            	db $FF,$FF,$FF,$FF  ; $8804
            	db $C3,$00,$00,$00  ; $8808
            	db $01,$F0,$F5,$FC  ; $880C
            	db $E5,$CD,$FE,$00  ; $8810
            	db $00,$00,$01,$F8  ; $8814
            	db $A0,$50,$A0,$60  ; $8818
            	db $50,$60,$70,$60  ; $881C
            	db $02,$E0,$0E,$0E  ; $8820
            	db $0E,$0E,$07,$06  ; $8824
            	db $04,$04,$02,$E8  ; $8828
            	db $0F,$31,$C1,$3D  ; $882C
            	db $4F,$3D,$01,$01  ; $8830
            	db $02,$F0,$0F,$30  ; $8834
            	db $C0,$3C,$DF,$3C  ; $8838
            	db $00,$00,$02,$F8  ; $883C
            	db $A0,$A0,$50,$40  ; $8840
            	db $60,$80,$C0,$C0  ; $8844
            	db $03,$E0,$03,$03  ; $8848
            	db $03,$03,$03,$01  ; $884C
            	db $01,$01,$03,$E8  ; $8850
            	db $02,$0C,$04,$03  ; $8854
            	db $00,$00,$00,$03  ; $8858
            	db $03,$F0,$00,$60  ; $885C
            	db $80,$00,$00,$00  ; $8860
            	db $00,$C0,$03,$F8  ; $8864
            	db $40,$80,$40,$80  ; $8868
            	db $80,$00,$80,$00  ; $886C
            	db $04,$E8,$07,$03  ; $8870
            	db $00,$80,$C0,$F0  ; $8874
            	db $FE,$FF,$04,$F0  ; $8878
            	db $E0,$C0,$00,$01  ; $887C
            	db $03,$0F,$7F,$FF  ; $8880
Pude haber ahorrado más de 200 bytes haciendo esto una subrutina y manteniendo un offset para repetir el dibujo. Creo que quería poner una cara diferente en el lado derecho de la pantalla, pero nunca lo hice.

L8884:      	LD HL,$2000
                LD DE,L88A9
            	LD B,$05
L888C:      	PUSH BC
            	LD B,$04
L888F:      	PUSH BC
            	LD B,$08
L8892:      	LD A,(DE)
            	CALL L0447
            	INC HL
            	DJNZ L8892
            	INC DE
            	POP BC
            	DJNZ L888F
            	LD A,H
            	INC A
            	LD H,A
            	LD A,$00
            	LD L,A
            	POP BC
            	DJNZ L888C
            	JP L88BD

L88A9:          db $1A,$1A,$1A,$1A  ; $88A9
            	db $1A,$16,$18,$1A  ; $88AD
            	db $1A,$16,$18,$1A  ; $88B1
            	db $1A,$16,$18,$1A  ; $88B5
            	db $1A,$16,$18,$1A  ; $88B9
Ahora procede a poner color en la cara. Parece que finalmente me di cuenta de como explorar un rectángulo en la pantalla y se puede ver LD B,$05 para dibujar 5 líneas, luego LD B,$04 para dibujar 4 columnas, y finalmente LD B,$08 para repetir el color en un caracter completo de 8x8.
Cualquier codificador experimentado de Z80 se estremecerá al ver 5 líneas de ensamblador que se podían reemplazar con INC H / LD L.0.
Cara de bitmap con color dibujada en Cubos
Cara de bitmap con color dibujada en Cubos.
Entonces repetí exactamente el mismo código para darle color a la cara de la derecha:

L88BD:      	LD HL,$20E0	; Point to the color data (right side)
                LD DE,L88A9	; Color data (reused)
            	LD B,$05	; 5 rows.
L88C5:      	PUSH BC
            	LD B,$04	; 4 columns.
L88C8:      	PUSH BC
            	LD B,$08	; 8x8 pixels.
L88CB:      	LD A,(DE)	; Color data.
            	CALL L0447	; Write to VRAM.
            	INC HL
            	DJNZ L88CB
            	INC DE
            	POP BC
            	DJNZ L88C8
            	LD A,$E0	; Prepare for next row.
            	LD L,A
            	POP BC
            	DJNZ L88C5
            	JP L8984
Al menos esta vez reutilizé los datos de color en L88A9. El código encadenado continua:

L8984:      	LD A,$F1
            	LD (LEF13),A
            	LD HL,$4010
            	LD DE,$4020
            	CALL L1500
            	LD HL,$4010
            	LD DE,$5010
            	CALL L1500
            	LD HL,$5010
            	LD DE,$5020
            	CALL L1500
            	LD HL,$4020
            	LD DE,$5020
            	CALL L1500
            	LD HL,$5810
            	LD DE,$5820
            	CALL L1500
            	LD HL,$5810
            	LD DE,$6810
            	CALL L1500
            	LD HL,$6810
            	LD DE,$6820
            	CALL L1500
            	LD HL,$6820
            	LD DE,$5820
            	CALL L1500
            	LD HL,$4028
            	LD DE,$4038
            	CALL L1500
            	LD HL,$4038
            	LD DE,$5038
            	CALL L1500
            	LD HL,$5028
            	LD DE,$5038
            	CALL L1500
            	LD HL,$4028
            	LD DE,$5028
            	CALL L1500
            	LD HL,$5828
            	LD DE,$5838
            	CALL L1500
            	LD HL,$5838
            	LD DE,$6838
            	CALL L1500
            	LD HL,$6838
            	LD DE,$6828
            	CALL L1500
            	LD HL,$5828
            	LD DE,$6828
            	CALL L1500
            	LD HL,$5020
            	LD DE,$5828
            	CALL L1500
            	LD HL,$5028
            	LD DE,$5820
            	CALL L1500
            	LD HL,$4414
            	LD DE,$441C
            	CALL L1500
            	LD HL,$4418
            	LD DE,$4C18
            	CALL L1500
            	LD HL,$442C
            	LD DE,$4830
            	CALL L1500
            	LD HL,$4434
            	LD DE,$4830
            	CALL L1500
            	LD HL,$4830
            	LD DE,$4C30
            	CALL L1500
            	LD HL,$5C2C
            	LD DE,$642C
            	CALL L1500
            	LD HL,$5C34
            	LD DE,$6434
            	CALL L1500
            	LD HL,$602C
            	LD DE,$6034
            	CALL L1500
            	LD HL,$5C14
            	LD DE,$5C1C
            	CALL L1500
            	LD HL,$5C14
            	LD DE,$6414
            	CALL L1500
            	LD HL,$6414
            	LD DE,$641C
            	CALL L1500
            	LD HL,$641C
            	LD DE,$611C
            	CALL L1500
            	LD HL,$6118
            	LD DE,$611C
            	CALL L1500
            	LD HL,$5C1C
            	LD DE,$5D1C
            	JP L8B97
Debido al valor $f1, pude distinguir que LEF13 contenía el código de color (blanco + negro). Los valores en HL y DE lucen extraños, nada como direcciones de VRAM, o direcciones de RAM, pero tenemos valores repetidos, y por supuesto, esto es una subrutina de dibujo de líneas en ROM (H=y1, L=x1, D=y2, E=x2)
Recuerdo vagamente que a principios de 1991 finalmente tuve una rutina de dibujo de líneas en la ROM de la computadora de mi padre.
Dibuja cuatro cuadrados y conecta estos con líneas diagonales. Dentro, dibuja letras para representar las teclas T, Y, G y H. Un pequeño manual visual.
Mapa de teclas dibujado por Cubos
Mapa de teclas dibujado por Cubos.
Solo un pequeño problema: No tengo la ROM para esta computadora, así que para este juego tuve que codificar otro algoritmo de línea Bresenham en Z80.

        ;
        ; Draw colored pixel
        ; D = Y-coordinate.
        ; E = X-coordinate.
        ;
L12DA:
        call coor2vdp	; Get coordinate and bitmask.
        ld c,a		; Save bitmask.
        call RDVRM	; Read VRAM.
        or c		; Set pixel.
        call WRTVRM	; Write VRAM.
        set 5,h		; Go to color area.
        ld a,(LEF13)	; Get current color.
        jp WRTVRM	; Set color for 8 pixels.

        ;
        ; Bresenham line drawing
        ; by Oscar Toledo G.
        ; Feel free to use it in your own projects.
        ; Just put my credit somewhere.
        ;
        ; H = Y1 coordinate.
        ; L = X1 coordinate.
        ; D = Y2 coordinate.
        ; E = X2 coordinate.
        ;
L1500:  
        ld a,d		
        sub h
        ld b,1
        jr nc,$+6
        ld b,-1
        neg
        exx
        ld b,a		; dy = abs(y2 - y1)
        exx

        ld a,e
        sub l
        ld c,1
        jr nc,$+6
        ld c,-1
        neg
        exx
        ld c,a		; dx = abs(x2 - x1)
        exx

        exx
        ld a,c
        cp b		; dx >= dy?
        jr c,.7		; No, jump.

        ld l,b
        ld h,0
        add hl,hl       ; 2*dy
        ld e,c
        ld d,0
        sbc hl,de       ; -dx
        exx

.1:
        push bc
        push de
        push hl
        ex de,hl
        call L12DA	; Draw a colored pixel on the screen.
        pop hl
        pop de
        pop bc
        ld a,h
        cp d
        jr nz,.2
        ld a,l
        cp e		; Reached endpoint?
        ret z		; Yes, return.
.2:     exx
        bit 7,h
        exx
        jr nz,.5
        ld a,h
        add a,b		; Displace Y-coordinate in Y direction.
        ld h,a
        exx
        ld e,c
        ld d,0
        sla e
        rl d
        sbc hl,de	; d -= dx * 2
        exx
.5:     ld a,l
        add a,c		; Displace X-coordinate in X direction.
        ld l,a
        exx
        ld e,b
        ld d,0
        sla e
        rl d
        add hl,de	; d += dy * 2
        exx
        jr .1

.7:
        ld l,c
        ld h,0
        add hl,hl       ; 2*dx
        ld e,b
        ld d,0
        sbc hl,de       ; -dy
        exx

.3:
        push bc
        push de
        push hl
        ex de,hl
        call L12DA	; Draw a colored pixel on the screen.
        pop hl
        pop de
        pop bc
        ld a,h
        cp d
        jr nz,.4
        ld a,l
        cp e		; Reached endpoint?
        ret z		; Yes, return.
.4:     exx
        bit 7,h		; d < 0?
        exx
        jr nz,.6	; Yes, jump.
        ld a,l
        add a,c		; Displace X-coordinate in X direction.
        ld l,a
        exx
        ld e,b
        ld d,0
        sla e
        rl d
        sbc hl,de	; d -= dy * 2
        exx
.6:     ld a,h
        add a,b		; Displace Y-coordinate in Y direction.
        ld h,a
        exx
        ld e,c
        ld d,0
        sla e
        rl d
        add hl,de	; d += dx * 2
        exx
        jr .3
El código termina cuando la última línea se dibuja, pero de alguna forma decidí insertar más código. Esta vez para mostrar el enemigo del nivel 1.

L8B97:      	CALL L1500
            	LD HL,$07D0
                LD DE,L8270
            	CALL L8BA6
            	JP L8BE2

L8BA6:      	LD B,$08
L8BA8:      	LD A,(DE)
            	CALL L8C06
            	INC HL
            	INC DE
            	DJNZ L8BA8
            	LD A,L
            	LD B,$08
            	SUB B
            	LD L,A
            	LD A,H
            	INC A
            	LD H,A
            	LD B,$08
L8BBA:      	LD A,(DE)
            	CALL L8C06
            	INC HL
            	INC DE
            	DJNZ L8BBA
            	LD A,H
            	DEC A
            	LD H,A
            	LD B,$08
L8BC7:      	LD A,(DE)
            	CALL L8C06
            	INC HL
            	INC DE
            	DJNZ L8BC7
            	LD A,L
            	LD B,$08
            	SUB B
            	LD L,A
            	LD A,H
            	INC A
            	LD H,A
            	LD B,$08
L8BD9:      	LD A,(DE)
            	CALL L8C06
            	INC HL
            	INC DE
            	DJNZ L8BD9
            	RET

L8C06:      	CALL L0447
            	PUSH HL
            	PUSH DE
            	LD DE,$2000
            	ADD HL,DE
            	LD A,$C1
            	AND $F0
            	OR $01
            	CALL L0447
            	POP DE
            	POP HL
            	RET

L8270:
            	db $03,$0F,$1F,$30  ; $8270
            	db $37,$69,$6F,$6C  ; $8274
            	db $6B,$37,$30,$DF  ; $8278
            	db $CF,$63,$60,$90  ; $827C
            	db $C0,$F0,$F8,$0C  ; $8280
            	db $EC,$96,$F6,$36  ; $8284
            	db $D6,$EC,$0C,$FB  ; $8288
            	db $F3,$C6,$06,$09  ; $828C
El registro HL apunta a la coordenada para dibujar el enemigo, y el registro DE apunta al bitmap del enemigo. El bitmap del enemigo está diseñado para un sprite, así que la subrutina L8BA6 lee el sprite como cuatro caracteres separados para ilustrar (observe los cuatro LD B,$08 separados).
Creo que quería que el primer enemigo luciera como un león, pero en lugar de eso, parece un monstruo con un abrigo... un abrigo muy grande.
Cuando escribe el byte gráfico también pone el color a verde oscuro usando la subrutina L8C06. Esta subrutina lee el color en el registro A ($c1 para verde oscuro con fondo negro) y entonces hace una operación muy loca... para acabar con el mismo valor.

L8BE2:      	LD HL,$0AD0
                LD DE,L85F8
            	CALL L8BA6
            	LD HL,$0DD0
                LD DE,L8620
            	CALL L8BA6
            	LD HL,$10D0
                LD DE,L8648
            	CALL L8BA6
            	LD HL,$13D0
                LD DE,L8670
            	JP L8BA6

L85F8:          db $06  
            	db $0F,$07,$05,$07  ; $85F9
            	db $03,$01,$0F,$1B  ; $85FD
            	db $1B,$1B,$03,$06  ; $8601
            	db $06,$06,$0E,$60  ; $8605
            	db $F0,$E0,$A0,$E0  ; $8609
            	db $C0,$80,$F0,$D8  ; $860D
            	db $D8,$D8,$C0,$60  ; $8611
            	db $60,$60,$70  ; $8615

L8620:
                db $0F,$3F  
            	db $09,$39,$0F,$3F  ; $8622
            	db $0B,$3C,$0F,$3F  ; $8626
            	db $0F,$3F,$0F,$3F  ; $862A
            	db $0F,$3F,$F0,$FC  ; $862E
            	db $90,$9C,$F0,$FC  ; $8632
            	db $D0,$3C,$F0,$FC  ; $8636
            	db $F0,$FC,$F0,$FC  ; $863A
            	db $F0,$FC  ; $863E

L8648:
                db $01,$03  
            	db $05,$07,$0F,$01  ; $864A
            	db $3F,$77,$77,$73  ; $864E
            	db $73,$F3,$0F,$06  ; $8652
            	db $06,$0E,$80,$C0  ; $8656
            	db $A0,$E0,$F0,$80  ; $865A
            	db $FC,$EE,$EE,$CE  ; $865E
            	db $CE,$CF,$F0,$60  ; $8662
            	db $60,$70  ; $8666

L8670:
                db $81,$41  
            	db $21,$11,$09,$05  ; $8672
            	db $03,$FE,$03,$05  ; $8676
            	db $09,$11,$21,$41  ; $867A
            	db $81,$00,$02,$04  ; $867E
            	db $08,$10,$20,$40  ; $8682
            	db $80,$FF,$80,$40  ; $8686
            	db $20,$10,$08,$04  ; $868A
            	db $02,$00  ; $868E
Seguramente pensé que sería agradable mostrar todos los enemigos que pueden aparecer, así que repetí las llamadas a L8BA6 con apuntadores a los gráficos de los otros enemigos.
Los enemigos que aparecen en Cubos
Los enemigos que aparecen en Cubos.
El enemigo para el segundo nivel es un chef (probablemente mi homenaje a Burgertime), y el tercero es un chip Z80, el cuarto es un verdugo (algo relacionado con la guillotina), y el quinto es una estrella, o tal vez un símbolo del omega, el final de todo, o más simplemente se me acabaron las ideas.
Después de este épico código parchado en cadena, retorna a donde fue llamado L86E0 y pone DE a $1080 y A con $05 y llama L8027.

L8027:      	LD B,A
L8028:      	PUSH BC
            	LD B,$06
L802B:      	NOP
            	CALL L80FD
            	NOP
            	INC D
            	NOP
            	DEC E
            	DJNZ L802B
            	LD B,$0C
L8037:      	PUSH DE
            	CALL L09B9
            	POP DE
            	INC D
            	DJNZ L8037
            	POP BC
            	DJNZ L8028
            	RET

L80FD:      	PUSH DE
            	CALL L09B9
            	POP DE
            	DEC E
            	PUSH DE
            	CALL L09B9
            	POP DE
            	RET
Esta subrutina dibuja 5 escalones yendo abajo y a la izquierda en la coordenada dada por DE (D es la coordenada Y y E es la coordenada X). Cada pixel es dibujado usando L09B9. La línea que va a la izquierda es dibujada como una línea escalonada de dos pixeles cada vez.
Puedo recordar vividamente cuando codifiqué esta subrutina de dibujar pixeles, pero no puedo recordar los detalles exactos, sin embargo para este artículo codifiqué un calculador de coordenada de pixeles (coor2vdp), una subrutina para escribir un pixel, una subrutina para borrar un pixel, y una subrutina para leer un pixel.

	;
	; Convert an X,Y coordinate to a bitmap VRAM address.
	; D = Y-coordinate.
	; E = X-coordinate.
	;
	; HL = Final VRAM address.
	; A = Pixel bitmask.
	;
coor2vdp:
        ld a,d
        rrca
        rrca
        rrca
        and $1f
        ld h,a
        ld a,e
        and $f8
        ld l,a
        ld a,d
        and $07
        or l
        ld l,a
        ld a,e
        and $07
        add a,pixel and 255
        ld e,a
        adc a,pixel>>8
        sub e
        ld d,a
        ld a,(de)
        ret

pixel:  db $80,$40,$20,$10,$08,$04,$02,$01

	; Set pixel.
L09B9:
        call coor2vdp
        ld c,a
        call RDVRM
        or c
        call WRTVRM
        RET

	; Erase pixel.
L09E4:  call coor2vdp
        cpl
        ld c,a
        call RDVRM
        and c
        jp WRTVRM

	; Test pixel.
L09FC:  call coor2vdp
        ld c,a
        call RDVRM
        and c
        RET
Primera 'escalera' dibujada en Cubos
Primera 'escalera' dibujada en Cubos.
El código continua en L8043 y hace una llamada a una subrutina similar.

L8043:      	LD DE,$1674
            	LD A,$05
            	CALL L804E
            	JP L806A

L804E:      	LD B,A
L804F:      	PUSH BC
            	LD B,$06
L8052:      	NOP
            	CALL L8109
            	NOP
            	INC D
            	NOP
            	INC E
            	DJNZ L8052
            	LD B,$0C
L805E:      	PUSH DE
            	CALL L09B9
            	POP DE
            	INC D
            	DJNZ L805E
            	POP BC
            	DJNZ L804F
            	RET

L8109:      	PUSH DE
            	CALL L09B9
            	POP DE
            	INC E
            	PUSH DE
            	CALL L09B9
            	POP DE
            	RET
La subrutina L804E hace exactamente lo mismo que L8027, excepto que la línea escalonada va a la derecha.
Segunda 'escalera' dibujada en Cubos
Segunda 'escalera' dibujada en Cubos.

L806A:      	LD B,$0C
L806C:      	PUSH DE
            	CALL L09E4
            	POP DE
            	DEC D
            	DJNZ L806C
            	LD DE,$1080
            	LD A,$05
            	CALL L804E
            	LD DE,$168C
            	LD A,$05
            	CALL L8027
            	NOP
            	NOP
            	NOP
            	JP L8095
Ahora borra la última línea vertical dibujada (12 pixeles). Luego dibuja dos escaleras internas, una yendo a la izquierda y otra yendo a la derecha.
Dos 'escaleras' más dibujadas en Cubos
Dos 'escaleras' más dibujadas en Cubos.
Continué dibujando la pirámide usando más código parcheado. Un síntoma de prueba constante y reajustes.

L8095:      	LD DE,$2274
            	LD A,$05
            	CALL L8151
            	LD DE,$2868
            	LD A,$04
            	CALL L804E
            	CALL L808A
            	LD DE,$3468
            	LD A,$04
            	CALL L804E
            	LD DE,$3A5C
            	LD A,$03
            	CALL L804E
            	CALL L808A
            	LD DE,$465C
            	LD A,$03
            	CALL L804E
            	CALL L808A
            	LD DE,$4C50
            	LD A,$02
            	CALL L804E
            	CALL L808A
            	LD DE,$5850
            	LD A,$02
            	CALL L804E
            	CALL L808A
            	LD DE,$5E44
            	LD A,$01
            	CALL L804E
            	NOP
            	NOP
            	NOP
            	LD DE,$228C
            	LD A,$05
            	CALL L8027
            	CALL L808A
            	LD DE,$2898
            	LD A,$04
            	CALL L8027
            	JP L8115

L8151:      	CALL L804E
            	JP L808A
Estado actual del dibujo de la pirámide en Cubos
Estado actual del dibujo de la pirámide en Cubos.
La pirámide empieza a tomar forma, y una vez dibujados varios escalones a la derecha, procedí a dibujar los escalones a la izquierda para completar la forma.

L8115:      	LD DE,$3498
            	LD A,$04
            	CALL L8027
            	CALL L808A
            	LD DE,$3AA4
            	LD A,$03
            	CALL L8027
            	LD DE,$46A4
            	LD A,$03
            	CALL L8027
            	CALL L808A
            	LD DE,$4CB0
            	LD A,$02
            	CALL L8027
            	LD DE,$58B0
            	LD A,$02
            	CALL L8027
            	CALL L808A
            	LD DE,$5EBC
            	LD A,$01
            	CALL L8027
            	JP L8157
En este punto a la pirámide solo le falta una línea diagonal a cada lado de la parte inferior.

L8157:      	LD DE,$6A44
            	LD A,$01	; Draw one step to the right.
            	CALL L804E
            	CALL L808A	; Erase last vertical line.
            	LD DE,$6ABC
            	LD A,$01
            	CALL L8027	; Draw one step to the left.
            	CALL L808A	; Erase last vertical line.
Este código termina la pirámide dibujando escalones (una línea diagonal + una línea vertical) pero en cada uno borra la línea vertical para no dejar líneas colgando.
Después de este penoso código, el niño pensó: ¿Por qué no dibujar las letras del título con líneas?

            	LD A,$74
            	LD (LEF13),A
            	LD DE,$A840
            	LD HL,$B840
            	CALL L1500
            	LD DE,$A840
            	LD HL,$A850
            	CALL L1500
            	LD DE,$B840
            	LD HL,$B850
            	CALL L1500
            	LD DE,$B058
            	LD HL,$B858
            	CALL L1500
            	LD DE,$B858
            	LD HL,$B868
            	CALL L1500
            	LD DE,$B068
            	LD DE,$B868
            	CALL L1500
            	LD HL,$B070
            	LD DE,$B07F
            	CALL L1500
            	LD HL,$B470
            	LD DE,$B47F
            	CALL L1500
            	LD HL,$B870
            	LD DE,$B87F
            	CALL L1500
            	LD HL,$B070
            	LD DE,$B870
            	CALL L1500
            	LD HL,$B180
            	LD DE,$B380
            	CALL L1500
            	LD HL,$B580
            	LD DE,$B780
            	CALL L1500
            	LD DE,$B068
            	LD HL,$B868
            	CALL L1500
            	LD HL,$B088
            	LD DE,$B098
            	CALL L1500
            	LD DE,$B098
            	LD HL,$B898
            	CALL L1500
            	LD DE,$B898
            	LD HL,$B888
            	CALL L1500
            	LD HL,$B888
            	LD DE,$B088
            	CALL L1500
            	LD DE,$B0A0
            	LD HL,$B0B0
            	CALL L1500
            	LD DE,$B0A0
            	LD HL,$B4A0
            	CALL L1500
            	LD HL,$B4A0
            	LD DE,$B4B0
            	CALL L1500
            	LD HL,$B4B0
            	LD DE,$B8B0
            	CALL L1500
            	LD HL,$B8A0
            	LD DE,$B8B0
            	CALL L1500
Pone el color a $74 (7 - Turquesa, 4 - Fondo azul) cuando hace esto el VDP crea una línea con color que sobresale, y lo usé para un efecto seudo-3D.
Letras de título para Cubos
Letras de título para Cubos.
Las líneas muestran el título "CUBOS".

            	LD A,(LFF0D)
            	CP $01
            	JP NZ,L8290
            	NOP
            	LD HL,$3800
                LD DE,L8250
            	LD BC,$0060
            	CALL L0309
            	JP L8290

L8250:          db $03,$05,$07,$03  ; $8250
            	db $01,$3F,$EF,$EF  ; $8254
            	db $C7,$C7,$03,$0F  ; $8258
            	db $0C,$0C,$0C,$3C  ; $825C
            	db $80,$40,$C0,$80  ; $8260
            	db $00,$F8,$EE,$EE  ; $8264
            	db $C6,$C6,$80,$E0  ; $8268
            	db $60,$60,$60,$78  ; $826C
L8270:
            	db $03,$0F,$1F,$30  ; $8270
            	db $37,$69,$6F,$6C  ; $8274
            	db $6B,$37,$30,$DF  ; $8278
            	db $CF,$63,$60,$90  ; $827C
            	db $C0,$F0,$F8,$0C  ; $8280
            	db $EC,$96,$F6,$36  ; $8284
            	db $D6,$EC,$0C,$FB  ; $8288
            	db $F3,$C6,$06,$09  ; $828C
Ahora si el nivel es 1, carga los sprites completos del juego en VRAM. Sin embargo, el contador en BC es para 3 sprites, y solo hay 2 sprites. ¡Pero no importa!

L8290:      	LD HL,$41C2
            	CALL L0100
            	LD HL,$1B00
            	LD A,$08
            	CALL L8918
            	LD A,$78
            	CALL L8918
            	LD A,$00
            	CALL L8918
            	LD A,$06
            	CALL L8918
            	LD A,$4C
            	CALL L8918
            	LD A,$00
            	LD (LFF10),A
            	LD A,$48
            	CALL L8918
            	LD A,$04
            	CALL L8918
            	LD A,R
            	CALL L8C1B
            	LD A,$4C
            	CALL L8918
            	LD A,$A8
            	CALL L8918
            	LD A,$04
            	CALL L8918
            	LD A,R
            	CALL L8C1B
            	LD A,$D1
            	CALL L8918
            	NOP

L8918:      	CALL L0447
            	INC HL
            	RET

L8C1B:      	LD A,R
            	AND $0F
            	CP $01
            	JP Z,L8C1B
            	CP $00
            	JP Z,L8C1B
            	JP L8918
Ahora pone el modo de 16x16 pixeles para sprites, y pone la posición inicial para el jugador y los dos enemigos. Realiza una pequeña optimización al cargar el VDP, llamando L8918 para escribir en VRAM y también incrementar la dirección del VDP. Los dos enemigos tienen un color seudoaleatorio generado por el registro R del Z80 (el contador de refresco de DRAM). Interesante que aparentemente olvidé que ya había leído el registro R, y dentro de L8C1B copio de nuevo el valor de R en A, y extrae los 4 bits bajos para el color y si el color es 0 ó 1 (transparente o negro) lee de nuevo R hasta tener un color válido.
Cubos después de poner los sprites
Cubos después de poner los sprites.

Movimiento del jugador

Ahora empezamos con el bucle principal, y lo primero que se hace es el movimiento del jugador.

L82E0:      	CALL L02AA
            	CP $54
            	JP Z,L838D
            	CP $59
            	JP Z,L8353
            	CP $47
            	JP Z,L82FD
            	CP $48
            	JP Z,L8376
Esta es una parte donde mi joven yo decepciona a mi viejo yo. Pude distinguir de inmediato los códigos ASCII para las letras T, Y, G y H, indicando que es el código de movimiento principal, y lo reemplacé con lectura del joystick (incluso en MSX que tiene un teclado), solo para ver que el juego terminaba de inmediato.
Después de un breve análisis, descubrí que el juego realiza unas pocas tareas y retorna a L82E0, así que en realidad procedía a una velocidad muy alta moviendo los enemigos sobre el jugador, y entonces agregué un gran retardo, y de esta forma vi que L02AA era una subrutina decodificadora de teclado que espera la tecla. Así que este juego Cubos es un juego basado en turnos, una vez que tecleas, la computadora responde.
Así que tuve que codificar una rutina lectora de joystick que se bloquea hasta que se hace un movimiento.

        ; Read keyboard
L02AA:
.0:
        ld bc,$0800	; Delay.
        dec bc
        ld a,b
        or c
        jr nz,$-3

        xor a		; Read keyboard movement.
        call GTSTCK
        or a
        jr z,.2
        ld a,1		; Read joystick 1 movement.
        call GTSTCK
.2:
        ld b,a
        ld a,(debounce)
        or a		; Is it debouncing?
        jr z,.1		; No, jump.
        dec a		; Yes, countdown.
        ld (debounce),a
        ld b,$ff	; No movement accepted.
.1:
        ld a,$00

        dec b		; 1-Up?
        jr nz,$+4
        ld a,$59	; ASCII Y

        dec b
        dec b		; 3-Right?
        jr nz,$+4
        ld a,$48	; ASCII H

        dec b
        dec b		; 5-Down?
        jr nz,$+4
        ld a,$47	; ASCII G

        dec b
        dec b		; 7-Left?
        jr nz,$+4
        ld a,$54	; ASCII T

        or a		; Any key pressed?
        jr z,.0		; No, jump and wait.
        push af
        ld a,15		; Start debouncing delay.
        ld (debounce),a
        pop af
        RET
Ahora el código para los movimientos respectivos:

L838D:      	LD HL,$1B00
            	CALL L0454
            	SUB $11
            	CALL L0447
            	INC HL
            	CALL L0454
            	SUB $0C
            	CALL L0447
            	JP L8311

L8353:      	LD HL,$1B00
            	CALL L0454
            	SUB $11
            	CALL L0447
            	INC HL
            	CALL L0454
            	ADD A,$0C
            	CALL L0447
            	JP L8311

L8376:      	LD HL,$1B00
            	CALL L0454
            	ADD A,$11
            	CALL L0447
            	INC HL
            	CALL L0454
            	ADD A,$0C
            	CALL L0447
            	JP L8311

L82FD:      	LD HL,$1B00
            	CALL L0454
            	ADD A,$11
            	CALL L0447
            	INC HL
            	CALL L0454
            	SUB $0C
            	CALL L0447
L8311:      	DEC HL
            	CALL L0454
            	ADD A,$0E
            	LD D,A
            	INC HL
            	CALL L0454
            	ADD A,$08
            	LD E,A
            	PUSH DE
            	CALL L8345
            	POP DE
            	CALL L86A8
            	JP L82F7
Para cada una de las cuatro subrutinas de movimiento lee las coordenadas X,Y del sprite del jugador, las desplaza por el monto correcto de pixeles (una combinación de 17 pixeles a la derecha o izquierda, y 12 pixeles arriba o abajo), y guarda las nuevas coordenadas X,Y en la VRAM. Entonces salta a L8311 y lee las nuevas coordenadas X,Y, llama a L8345 para determinar si marca el nuevo cubo de la pirámide.

L8345:      	PUSH DE
            	CALL L09FC
            	POP DE
            	PUSH AF
            	CALL L09B9
            	POP AF
            	RET NZ
            	JP L8371

L8371:          LD HL,LFF0F
            	INC (HL)
            	RET
Para determinar si debe llenar un nuevo cubo de la pirámide, primero llama L09FC para leer el pixel debajo de la coordenada D,E, y entonces llama a L09B9 para dibujar un pixel en la misma posición. Retorna si el pixel ya estaba marcado o de lo contrario salta a L8371 para incrementar el contador en LFF0F (probablemente un contador de cuantos cubos se han llenado).

L86A8:      	INC E
            	CALL L869C
            	INC E
            	CALL L869C
            	INC E
            	CALL L869C
            	INC E
            	CALL L869C
            	INC D
            	NOP
            	CALL L869C
            	DEC E
            	CALL L869C
            	DEC E
            	CALL L869C
            	DEC E
            	CALL L869C
            	INC D
            	DEC E
            	CALL L869C
            	INC E
            	CALL L869C
            	INC E
            	CALL L869C
            	INC E
            	CALL L869C
            	RET

L869C:      	LD A,$F1
            	LD (LEF13),A
            	PUSH DE
            	CALL L12DA
            	POP DE
            	RET
Dibuja algo similar a una huella en el cubo, pixel por pixel, y la subrutina L869C pone el color para la huella y dibuja un pixel coloreado.
Ahora para el gran momento vergonzoso: ¡Nunca verifica si el cuadro destino del jugador es uno válido! El jugador fácilmente puede caminar en el cielo y ganar sin ser molestado por los enemigos. ¡Huy!

Movimiento de los enemigos

Después de mover el jugador, salta a L82F7 para mover los enemigos:

L82F7:      	CALL L890A
            	JP L843E

L890A:      	CALL L832A
            	LD A,E
            	RET

L832A:      	CALL L83A4
            	LD E,A
            	NOP
            	NOP
            	JP L88E0

L83A4:      	LD A,(LFF0F)
            	CP $0F
            	JP Z,L85C1
            	JP L83EB

L83EB:      	LD A,(LFF0E)
            	CPL
            	LD (LFF0E),A
            	CP $FF
            	RET Z
            	LD HL,$1B00
            	JP L83AF

L83AF:      	CALL L0454
            	LD B,A
            	LD HL,$1B04
            	CALL L0454
            	CP B
            	JP C,L83C5
            	SUB $11
L83BF:      	CALL L0447
            	JP L83CA

L83C5:      	ADD A,$11
            	JP L83BF

L83CA:      	LD HL,$1B01
            	CALL L0454
            	LD B,A
            	LD HL,$1B05
            	CALL L0454
            	CP B
            	JP C,L83E3
            	SUB $0C
L83DD:      	CALL L0447
            	JP L83E8

L83E3:      	ADD A,$0C
            	JP L83DD

L83E8:      	JP L8402
En una demostración aterradora de código parcheado, realiza tres llamadas encadenadas hasta que alcanza L83A4 y si el jugador ha llenado los 15 cubos de la pirámide entonces ha ganado el nivel actual y salta a L85C1 (después se estudiará). De nuevo, un parche salta a L83EB y complementa el contenido de LFF0E, si es $ff entonces no mueve los enemigos en este turno (haciendo RET Z). Esto significa que el jugador puede moverse dos veces antes de que los enemigos se muevan. Entonces salta de nuevo a L83AF con HL puesto a $1b00 (atributos de sprites en VRAM para el jugador).
Obtiene la coordenada Y para el jugador y la coordenada Y para el enemigo 1 y si el enemigo está debajo del jugador, va arriba (SUB $11), de lo contrario va abajo (ADD A,$11). Entonces actualiza la coordenada Y del enemigo en L83BF, y salta a L83CA.
L83CA obtiene la coordenada X para el jugador y la coordenada X del enemigo 1 y si el enemigo está a la derecha del jugador, se va a la izquierda (SUB $0C), de lo contrario se va a la derecha (ADD A,$0C). Y actualiza la coordenada X del enemigo en L83DD, y salta a L8402.
El resultado de esto es que el enemigo se mueve en diagonal siguiendo al jugador.
Y entonces el código se repite embarazosamente para mover el segundo enemigo.

L8402:      	LD HL,$1B00
            	CALL L0454
            	LD B,A
            	LD HL,$1B08
            	CALL L0454
            	CP B
            	JP C,L841B
            	SUB $11
L8415:      	CALL L0447
            	JP L8420

L841B:      	ADD A,$11
            	JP L8415

L8420:      	LD HL,$1B01
            	CALL L0454
            	LD B,A
            	LD HL,$1B09
            	CALL L0454
            	CP B
            	JP C,L8439
            	SUB $0C
L8433:      	JP L0447

L8439:      	ADD A,$0C
            	JP L8433
El final del código salta directamente en L0447 y usa una característica del apuntador de pila donde L0447 contiene la instrucción RET para volver al punto de llamada.

El tercer enemigo

El juego no retorna directamente al punto de llamada en L82F7, en lugar de eso salta a L88E0.

L88E0:      	LD A,(LFF10)
            	CP $FE
            	JP Z,L8926
            	LD C,$7F
            	CALL L891D
            	NOP
            	NOP
            	NOP
            	LD HL,$1B0C
            	LD A,$08
            	CALL L0447
            	INC HL
            	LD A,$78
            	CALL L0447
            	INC HL
            	LD A,$04
            	CALL L0447
            	INC HL
            	LD A,$0E
            	JP L890F

L890F:      	CALL L0447
            	LD A,$FE
            	LD (LFF10),A
            	RET

L891D:      	CALL L8333
            	CP $40
            	RET NC
            	POP IX
            	RET

L8333:      	LD A,R
            	CP C
            	RET C
            	SRA A
            	CP C
            	RET C
            	PUSH BC
            	LD B,A
L833D:      	DJNZ L833D
            	POP BC
            	JR L8333
Primero checa si la variable FF10 es $fe para saltar a L8926, pero como fue inicializada a cero, entonces pone C a $7f y llamada L891D y en un encadenamiento a L8333 para ver si el número aleatorio es mayor o igual que $40 para crear un tercer enemigo apareciendo el tope de la pirámide (la serpiente en Q*Bert), y si es menor de $40 se traga la dirección de retorno de la pila (POP IX) y retorna sin hacer nada. Esto significa que el tercer enemigo tiene un chance de aparición en el tope de la pirámide de 50% por cada movimiento del jugador.
Como el valor del registro R es comparado contra C (que contiene $7f), casi siempre retorna sin cambios en L8333 con RET C debido a que R es $00-$7f, y el resto del código solo se usa si R es $7f en una sorprendente falta de conocimiento de mi lado de como R funciona.
El enemigo es creado en el tope de la pirámide con el mismo sprite de enemigo (una oportunidad perdida de mostrar otro tipo de enemigo) y color gris fijo, también encadena a L890F para marcar el enemigo como inicializado (poniendo LFF10 a $fe).

Movimiento del tercer enemigo

Si el tercer enemigo está presente (LFF10 contiene $fe), el código salta a L8926.

L8926:      	LD HL,$1B0C
            	CALL L0454
            	ADD A,$11
            	CALL L8971
            	LD C,$7F
            	CALL L8333
            	CP $3F
            	JR C,L8945
            	LD HL,$1B0D
            	CALL L0454
            	SUB $0C
            	JP L894D

L8945:      	LD HL,$1B0D
            	CALL L0454
            	ADD A,$0C
L894D:      	CALL L0447
            	LD HL,$1B00
            	CALL L0454
            	LD B,A
            	LD HL,$1B0C
            	CALL L0454
            	CP B
            	RET NZ
            	LD HL,$1B01
            	CALL L0454
            	LD B,A
            	LD HL,$1B0D
            	CALL L0454
            	CP B
            	RET NZ
            	JP L8480

L8971:      	CALL L0447
            	CP $5D
            	RET NZ
            	LD A,$D1
            	CALL L0447
            	LD A,$00
            	LD (LFF10),A
            	POP IX
            	RET
La coordenada Y para el tercer enemigo se lee de VRAM $1b0c y se le agrega $11 para hacerlo avanzar verticalmente, y si alcanza $5d (comparación en L8971), se le hace desaparecer poniendo su coordenada Y en $d1 (fuera del rango visual), poniendo LFF10 a $00, y se traga la dirección de retorno (POP IX) y vuelve a donde se le llamó.
Si no ha alcanzado $5d, entonces genera un número aleatorio y si es menor que $3f entonces agrega $0c a la coordenada X, de lo contrario sustrae $0c a la coordenada X, haciendolo oscilar horizontalmente sobre la pirámide.
En L894D hace una comparación de la coordenada Y del jugador contra la coordenada Y del enemigo, y si ambas son iguales entonces hace la comparación de coordenadas X, y en caso de colisión salta a L8480.

Colisión del enemigo

Después de completar los enemigos y volver al punto de llamada en L82F7, salta a L843E.
El código luce como detección de colisión con enemigos.

L843E:      	LD HL,$1B00
            	CALL L0454
            	LD B,A
            	LD HL,$1B04
            	CALL L0454
            	CP B
            	CALL Z,L845C
            	LD HL,$1B08
            	CALL L0454
            	CP B
            	CALL Z,L846E
            	JP L82E0

L845C:      	LD HL,$1B01
            	CALL L0454
            	LD C,A
            	LD HL,$1B05
            	CALL L0454
            	CP C
            	JP Z,L8480
            	RET

L846E:      	LD HL,$1B01
            	CALL L0454
            	LD C,A
            	LD HL,$1B09
            	CALL L0454
            	CP C
            	JP Z,L8480
            	RET
Lee la coordenada Y del jugador de la VRAM y hace una comparación contra la coordenada Y de los enemigos, si alguno de estas es igual entonces llama a una subrutina extra en L845C o L846E para hacer una comparación de la coordenada X, y si alguna de estas es igual entonces salta a L8480 (jugador derrotado).

Jugador derrotado

Cuando algún enemigo toca al jugador, el juego salta a L8480.

L8480:      	LD A,$F6
            	LD (LEF13),A
            	LD HL,$8020
            	LD DE,$9020
            	CALL L1500
            	LD HL,$8020
            	LD DE,$8030
            	CALL L1500
            	LD HL,$8820
            	LD DE,$8830
            	CALL L1500
            	LD HL,$8030
            	LD DE,$8830
            	CALL L1500
            	LD HL,$8438
            	LD DE,$8638
            	CALL L1500
            	LD HL,$8838
            	LD DE,$9038
            	CALL L1500
            	LD HL,$8840
            	LD DE,$8850
            	CALL L1500
            	LD HL,$8840
            	LD DE,$9040
            	CALL L1500
            	LD HL,$9040
            	LD DE,$9050
            	CALL L1500
            	LD HL,$8C40
            	LD DE,$8C50
            	CALL L1500
            	LD HL,$8858
            	LD DE,$9058
            	CALL L1500
            	LD HL,$8858
            	LD DE,$8868
            	CALL L1500
            	LD HL,$8868
            	LD DE,$8C68
            	CALL L1500
            	LD HL,$8C58
            	LD DE,$8C68
            	CALL L1500
            	LD HL,$8C58
            	LD DE,$9068
            	CALL L1500
            	LD DE,$8870
            	LD HL,$887C
            	CALL L1500
            	LD DE,$9070
            	LD HL,$907C
            	CALL L1500
            	LD DE,$8870
            	LD HL,$9070
            	CALL L1500
            	LD HL,$8A80
            	LD DE,$8E80
            	CALL L1500
            	LD HL,$887C
            	LD DE,$8A80
            	CALL L1500
            	LD DE,$8E80
            	LD HL,$907C
            	CALL L1500
            	LD DE,$8888
            	LD HL,$9088
            	CALL L1500
            	LD DE,$8888
            	LD HL,$8898
            	CALL L1500
            	LD DE,$8C88
            	LD HL,$8C98
            	CALL L1500
            	LD DE,$9088
            	LD HL,$9098
            	CALL L1500
            	LD HL,$88A0
            	LD DE,$88B0
            	CALL L1500
            	LD HL,$8CA0
            	LD DE,$8CB0
            	CALL L1500
            	LD HL,$90A0
            	LD DE,$90B0
            	CALL L1500
            	LD HL,$88A0
            	LD DE,$8CA0
            	CALL L1500
            	LD HL,$8CB0
            	LD DE,$90B0
            	CALL L1500
            	LD HL,$84C0
            	LD DE,$8BC0
            	CALL L1500
            	LD HL,$8DC0
            	LD DE,$90C0
            	CALL L1500
            	LD B,$05
L85A7:      	PUSH BC
            	LD BC,$0000
L85AB:      	DEC BC
            	LD A,B
            	OR C
            	JR NZ,L85AB
            	POP BC
            	DJNZ L85A7
            	CALL L0109
            	JP L8000
Jugador derrotado en Cubos
Jugador derrotado en Cubos.
Dibuja las letras "PIERDES!" usando vectores (o el mismo significado, montones de líneas) en blanco con fondo rojo, de nuevo para el efecto seudo-3D.
Tiene un retardo largo (L85A7) y entonces salta de nuevo al inicio del juego en L8000.

Nivel terminado

Cuando todos los cubos de la pirámide son llenados, incrementa el número de nivel en LFF0D.

L85C1:          LD HL,LFF0D
            	INC (HL)
            	LD A,(HL)
            	CP $02
            	JP Z,L85DF
            	CP $03
            	JP Z,L8618
            	CP $04
            	JP Z,L8640
            	CP $05
            	JP Z,L8668
            	CP $06
            	JP Z,L8690
L85DF:          LD DE,L85F8
L85E2:      	LD HL,$3820
            	LD BC,$0020
            	CALL L0309
            	XOR A
            	LD (LFF0F),A
            	LD (LFF0E),A
            	JP L8AA9

L8618:          LD DE,L8620
            	JP L85E2

L8640:          LD DE,L8648
            	JP L85E2

L8668:          LD DE,L8670
            	JP L85E2

L8690:      	LD A,$01
            	LD (HL),A
            	JP L8000
Dependiendo en el número de nivel, selecciona el sprite para los siguientes enemigos que serán ilustrados, reinicia el número de cubos llenados y el turno de enemigos. Cuando el sprite se cambia ocurre un bug que se puede ver brevemente cuando los enemigos actuales se convierten en los nuevos enemigos.
En un obvio pensamiento posterior, salta a L8AA9 para mostrar otro mensaje vectorial:

L8AA9:      	LD A,$FD
            	LD (LEF13),A
            	LD HL,$8038
            	LD DE,$9038
            	CALL L1500
            	LD HL,$8038
            	LD DE,$8048
            	CALL L1500
            	LD HL,$8048
            	LD DE,$8348
            	CALL L1500
            	LD HL,$9038
            	LD DE,$9048
            	CALL L1500
            	LD DE,$8948
            	LD HL,$9048
            	CALL L1500
            	LD HL,$8940
            	LD DE,$8948
            	CALL L1500
            	LD HL,$8850
            	LD DE,$8860
            	CALL L1500
            	LD HL,$8860
            	LD DE,$9060
            	CALL L1500
            	LD HL,$8850
            	LD DE,$9050
            	CALL L1500
            	LD HL,$8C50
            	LD DE,$8C60
            	CALL L1500
            	LD HL,$8868
            	LD DE,$9068
            	CALL L1500
            	LD HL,$8878
            	LD DE,$9078
            	CALL L1500
            	LD HL,$8868
            	LD DE,$9078
            	CALL L1500
            	LD HL,$8880
            	LD DE,$8890
            	CALL L1500
            	LD HL,$8880
            	LD DE,$9080
            	CALL L1500
            	LD HL,$8890
            	LD DE,$9090
            	CALL L1500
            	LD HL,$8C80
            	LD DE,$8C90
            	CALL L1500
            	LD HL,$8898
            	LD DE,$88A8
            	CALL L1500
            	LD HL,$8898
            	LD DE,$8C98
            	CALL L1500
            	LD HL,$8C98
            	LD DE,$8CA8
            	CALL L1500
            	LD HL,$8CA8
            	LD DE,$90A8
            	CALL L1500
            	LD HL,$9098
            	LD DE,$90A8
            	CALL L1500
            	LD HL,$84B8
            	LD DE,$8CB8
            	CALL L1500
            	LD HL,$8EB8
            	LD DE,$90B8
            	CALL L1500
            	LD B,$05
L8B88:      	PUSH BC
            	LD BC,$0000
L8B8C:      	DEC BC
            	LD A,B
            	OR C
            	JR NZ,L8B8C
            	POP BC
            	DJNZ L8B88
            	JP L8003
El jugador termina un nivel en Cubos
El jugador termina un nivel en Cubos. Se puede ver el bug de que los enemigos del siguiente nivel son redefinidos demasiado pronto.
El mensaje es GANAS! y hace otro enorme retardo en L8B88, y salta atrás para volver a mostrar la pirámide.
Sin embargo, para el último nivel nunca muestra el mensaje GANAS! porque salta directo a L8690 que reinicia el nivel 1 y salta al inicio del juego (¿porqué hice eso?).

Variables en memoria

Las variables de memoria utilizadas para este juego son admirablemente escasas.

        ORG RAM_BASE

LEF13:      RB 1	; Color para dibujar pixeles.
LFF0D:      RB 1	; Número de nivel.
LFF0E:      RB 1	; Turno de los enemigos.
LFF0F:      RB 1	; Número de cubos llenos.
LFF10:      RB 1	; Estatus del tercer enemigo.
			; ($00- No creado, $fe- Creado)

debounce:       rb 1

STACK:  EQU RAM_BASE+1024
Las coordenadas del jugador y los enemigos son preservadas en VRAM.

Interludio

Es bastante claro que todavía no hacía planes para escribir un juego, asi que los parches se acumulaban haciendo muy difícil seguir el código.
Muchas subrutinas largas pudieron ser usadas para simplificar el juego. Tener las coordenadas del jugador y enemigos en RAM hubiera simplificado el juego, y permitido hacer un juego en tiempo real con el característico salto del jugador y los enemigos.
La falta de detección de frontera para el jugador fue un error trágico que pudo ser evitado teniendo una tabla para cada posible coordenada de cubo en la pirámide, sin embargo, estoy muy seguro de que el código parcheado jugó en mi contra haciendo cada vez más difícil ver donde tenía que parchear el código. La solución obvia hubiera sido que cada tecla cargara un registro con el desplazamiento requerido, y una sola rutina estaría a cargo de mover el jugador y ver si la posición destino es válida.
El dibujo vectorial de líneas para las letras pudo beneficiarse de una tabla de origen-destino-destino salvando un montón de llamadas a la subrutina de dibujo de lineas.
Fue también una oportunidad desperdiciada no tener un cuadro especial para un enemigo cayendo de arriba, o diferentes cuadros para las direcciones del jugador, o al menos un cuadro diferente cuando el jugador es tocado por los enemigos. Al menos esta vez todos mis gráficos fueron originales.
Tampoco hay efectos de sonido, probablemente debido a que el juego se detiene cada vez a esperar por un movimiento.
Este iba a ser el epílogo, sin embargo, no estaba satisfecho simplemente con mostrar mi viejo juego defectuoso. ¡Necesitaba corregirlo! Así que aquí vamos.

Dibujando la pirámide

En lugar de dibujar la pirámide usando el código tipo escaleras, preferí usar una subrutina para dibujar un cubo seudo-3D para dibujar cada uno de los quince cubos en la pirámide.

        ;
        ; Draw a row of cubes.
        ;
.2:
        push bc
        push de
        call draw_cube
        pop de
        ld a,e
        add a,$18	; Move X-coordinate.
        ld e,a
        pop bc
        djnz .2
        
        pop de
        ld a,e
        sub $0c		; Half to the left.
        ld e,a
        ld a,d
        add a,$11	; Next row.
        ld d,a
        pop bc
        inc b		; Increase number of cubes.
        ld a,b
        cp 6		; Reached six cubes?
        jp nz,.1	; No, continue drawing.
El primer paso es dibujar la pirámide en líneas. La primera línea contiene un cubo, la segunda contiene dos cubos, e iterativamente hasta que dibuja cinco cubos en la quinta línea. El código para dibujar un cubo seudo-3D es este:

draw_cube:
        push de             ; Drawing schematic:
        call draw_left      ; 1   1/ \5        
        push de             ;     /   \
        call draw_vertical  ; 2 2|\4  /|6
        call draw_right     ; 3  | \ /8|
        pop de              ;    3\ I9/7
        call draw_right     ; 4    \I/
        pop de
        call draw_right     ; 5
        push de
        call draw_vertical  ; 6
        call draw_left      ; 7
        pop de
        call draw_left      ; 8
        jp draw_vertical    ; 9

	;
	; Draw a diagonal line to the left.
	;
draw_left:
        ld b,$06
.1:     call draw_pixel
        dec e
        call draw_pixel
        dec e
        inc d
        djnz .1
        ret

	;
	; Draw a diagonal line to the right.
	;
draw_right:
        ld b,$06
.1:     call draw_pixel
        inc e
        call draw_pixel
        inc e
        inc d
        djnz .1
        ret

	;
	; Draw a vertical line.
	;
draw_vertical:
        ld b,$0b
.1:     call draw_pixel
        inc d
        djnz .1
        ret

draw_pixel:
        push de
        call L09B9
        pop de
        ret
Sólo con este cambio ahorramos 243 bytes.
El siguiente cambio fue definir el sprite de enemigo en el comienzo del nivel y poner juntos en memoria todos los bitmaps de enemigos, así que cargar el enemigo en la VRAM es un asunto de multiplicar el nivel por 32 y añadir un offset, también agregué un nuevo dibujo de sprite para el enemigo del tope (¿adivina cuál? ¡una víbora! El sprite viene de Viboritas). Esto permitió también eliminar el bug de no mostrar el mensaje ganador en el último nivel. Incluso cuando agregamos un nuevo sprite, el tamaño del juego se redujo debido a la eliminación de código ineficiente.
Codifiqué una lista de posiciones válidas en la pirámide (los quince cubos completos), y reemplacé el código de movimiento con una selección de offsets para moverse, entonces lee las coordenadas actuales del jugador, las desplaza por el offset, y verifica si es un movimiento válido. Si el movimiento es válido entonces actualiza la posición del jugador, y verifica si debe dibujar la huella. Si el movimiento no es válido entonces el jugador no se mueve.

L82E0:
	call L02AA	; Read the keyboard.
	cp $54
	jr nz,$+5
	ld de,$eff4	; y = -17, x = -12
	cp $59
	jr nz,$+5
	ld de,$ef0c	; y = -17, x = +12
	cp $47
	jr nz,$+5
	ld de,$11f4	; y = +17, x = -12
	cp $48
	jr nz,$+5
	ld de,$110c	; y = +17, x = +12

	ld hl,$1b00
	call L0454	; Read Y-coordinate of the player.
	add a,d		; Add Y offset.
	ld d,a
	inc hl
	call L0454	; Read X-coordinate of the player.
	add a,e		; Add X offset.
	ld e,a

	ld hl,valid_positions
	ld b,15
.1:
	ld a,(hl)
	cp d
	inc hl
	jr nz,.2
	ld a,(hl)
	cp e
	jr z,.3
.2:	
	inc hl
	djnz .1
	jp L82F7	; Invalid movement.

	; Valid movement.
.3:
	ld hl,$1b00
	ld a,d
	call L0447	; Update Y-coordinate of the player.
	inc hl
	ld a,e
	call L0447	; Update X-coordinate of the player.
	ld a,d
	add a,$0e
	ld d,a
	ld a,e
	add a,$06
	ld e,a
	push de
	call L8345	; Check if it filled another pyramid square.
	pop de
	call L86A8	; Draw footprint.
L82F7:
      	CALL L832A
        jp L843E

valid_positions:
	db $08,$78
	db $19,$6c,$19,$84
	db $2a,$60,$2a,$78,$2a,$90
	db $3b,$54,$3b,$6c,$3b,$84,$3b,$9c
	db $4c,$48,$4c,$60,$4c,$78,$4c,$90,$4c,$a8

Cada uno de los cuatro movimientos posibles se pone como dos offsets en el registro DE, y añadido a la posición X,Y del jugador. Ahora hace una comparación contra todas las posiciones válidas sobre la pirámide (la tabla es valid_positions). Si el movimiento es inválido simplemente salta a mover los enemigos, de lo contrario actualiza las coordenadas X,Y del jugador.
Un excelente premio por hacer este cambio es que se eliminó el confuso código replicado para leer las coordenadas X,Y del jugador en cada movimiento ¡También ahorró un montón de bytes!
Finalmente, nunca me gustó la cara que dibuje para las esquinas, así que hice otra y reemplacé completamente el código de dibujo con una sola subrutina para copiar un rectángulo (usada cuatro veces).

    ;
	; Draw faces.
	;
	ld hl,face_bitmaps
	ld de,$0000
	call copy_rectangle
	ld hl,face_bitmaps
	ld de,$00e0
	call copy_rectangle
	ld hl,face_colors
	ld de,$2000
	call copy_rectangle
	ld hl,face_colors
	ld de,$20e0
	call copy_rectangle

copy_rectangle:
	ld b,4
.1:	push bc
	push de
	ex de,hl
	ld b,32
.2:	ld a,(de)
	call L0447
	inc hl
	inc de
	djnz .2
	ex de,hl
	pop de
	inc d
	pop bc
	djnz .1
	ret
	
		;
	; Bitmaps and color for the face.
	;
face_bitmaps:
 db $ff,$ff,$fc,$fe,$ff,$f9,$fd,$fe
 db $f1,$a4,$a9,$52,$54,$55,$3f,$7f
 db $2d,$09,$22,$00,$92,$20,$ff,$fc
 db $ff,$3f,$2f,$8f,$1f,$4f,$1f,$2f
 db $f9,$fc,$fe,$f9,$fa,$f8,$fa,$fa
 db $3f,$ff,$3f,$bf,$c7,$81,$1f,$c1
 db $ff,$fc,$ff,$ff,$e3,$81,$fc,$83
 db $8f,$0f,$af,$1f,$5f,$1f,$5f,$5f
 db $fa,$fa,$fc,$fe,$fe,$fe,$fe,$ff
 db $b3,$ff,$fe,$fe,$fd,$fd,$fe,$7f
 db $e5,$ff,$ff,$ff,$ff,$7f,$ff,$fe
 db $5f,$5f,$3f,$7f,$7f,$7f,$7f,$ff
 db $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff
 db $7c,$7c,$bf,$df,$ef,$f7,$f8,$f8
 db $3e,$3e,$fc,$f8,$f0,$e0,$ff,$ff
 db $ff,$ff,$ff,$ff,$ff,$ff,$ff,$ff
face_colors:
 db $a1,$a1,$a1,$a1,$a1,$a1,$a1,$a1
 db $a1,$a1,$a1,$a1,$a1,$a1,$b1,$b1
 db $a1,$a1,$a1,$f1,$a1,$a1,$91,$91
 db $a1,$a1,$a1,$a1,$a1,$a1,$a1,$a1
 db $a1,$a1,$a1,$a1,$a1,$a1,$a1,$a1
 db $b1,$b1,$b1,$b1,$b1,$b1,$b1,$b1
 db $91,$91,$91,$91,$91,$91,$91,$91
 db $a1,$a1,$a1,$a1,$a1,$a1,$a1,$a1
 db $a1,$a1,$a1,$a1,$a1,$a1,$a1,$a1
 db $b1,$b1,$b1,$b1,$b1,$b1,$b1,$b1
 db $91,$91,$91,$91,$91,$91,$91,$91
 db $a1,$a1,$a1,$a1,$a1,$a1,$a1,$a1
 db $a1,$a1,$a1,$a1,$a1,$a1,$a1,$a1
 db $b1,$b1,$b1,$b1,$b1,$b1,$b1,$b9
 db $91,$91,$98,$98,$98,$98,$81,$81
 db $a1,$a1,$a1,$a1,$a1,$a1,$a1,$a1
Tambien removí unos pocos saltos encadenados y la versión actualizada del juego es 547 bytes más corta y más fácil de entender.
Cubos actualizado y corregido (v2)
Cubos actualizado y corregido (v2).

Epílogo

Mirar el código que escribí en mis primeros años me dio un gran panorama de como solucionaba los problemas de diseño.
Mi entusiasmo juvenil me mantenía enfocado en un problema, y si no podía encontrar una solución, simplemente seguía adelante codificando otras partes del juego.
Pero este mismo entusiasmo evitó que usara un poco de tiempo para hacer un plan del juego. Esta sola cosa pudo salvarme horas de errores en desarrollo, y me hubiera ayudado a hacer juegos más profesionales más pronto.
Veo ahora que al venir del lenguaje BASIC, codificaba como lo hacía en el lenguaje BASIC: Agregaba cosas hasta que funcionaba, excepto que en BASIC puedes insertar líneas de´código e incluso renumerar las líneas de código. Podemos ver BASIC como un lapiz, puedes borrar para insertar cosas o reescribirlas, pero el código máquina es como una pluma, no puedes corregir fácilmente tus errores.

Descargas

Ligas relacionadas

Última modificación: 19-feb-2024