El código

Mapa de dónde está cada cosa. Para el listado completo de rutinas y datos, INVENTARIO.txt.

Mapa de memoria

Con el juego corriendo, el MSX queda así:

0x0000 ┌──────────────────────────┐
       │  ROM del BASIC           │  NO se toca: el juego sigue llamando
       │                          │  a la BIOS (dibujar en pantalla, leer
0x4000 ├──────────────────────────┤  el mando, mover bloques de memoria)
       │  Gráficos                │  fuente, colores, sprites
0x5C00 │  Pantalla de menú        │
0x5FC0 │  Marcador                │
0x60C0 │  Animaciones y física    │  50 ranuras de 128 bytes
0x79C0 │  Puntos ocultos          │  por pantalla
0x7BA0 │  Enemigos                │  por pantalla
0x7D80 │  Buffer del mapa actual  │  512 bytes
0x8000 ├──────────────────────────┤
       │  CÓDIGO DEL JUEGO        │  ~5 KB
0x8F00 │  Variables               │
0x8FA0 │  Pila                    │  crece hacia abajo desde 0x8FFF
0x9000 ├──────────────────────────┤
       │  29 mapas de pantalla    │  512 bytes cada uno
0xCA00 ├──────────────────────────┤
       │  (código muerto)         │  restos de otra compilación
0xD000 ├──────────────────────────┤
       │  Reproductor de sonido   │
0xD760 │  Música del menú         │
0xDB00 │  Rutina de cada frame    │
0xDE01 └──────────────────────────┘

Que la ROM del BASIC siga puesta es una decisión de diseño: el juego se apoya en las rutinas del sistema para las tareas comunes en vez de reimplementarlas, y a cambio renuncia a 16 KB de memoria.

El bucle principal

Todo el juego cabe en esto:

BUCLE_PARTIDA:
    call 0832ah         ; ¿han pulsado CTRL+STOP?
    halt                ; espera al barrido de pantalla
    call 0db00h         ; efectos de sonido (motor PSG)
    halt
    call 0db00h
    call DISPARA
    call MUEVE_DISPAROS
    call MUEVE_ENEMIGOS
    call COMPRUEBA_COLISION_SPRITES
    ...
    call MUEVE_JUGADOR
    call COLISIONES_MAPA
    jp BUCLE_PARTIDA

Dos halt por vuelta significa que el juego va a 25 o 30 fotogramas por segundo, la mitad del refresco de pantalla. Es una decisión deliberada: da tiempo a mover todo sin que se note tirones.

Cómo se mueve el monje

El mando se traduce a un código de acción con una tabla, y ese código es a la vez el índice de la animación y el estado del personaje:

Dirección del mandoCódigoQué hace
centro, abajo, abajo±diagonal0x00quieto (agacharse no existe)
derecha0x01andar derecha
izquierda0x02andar izquierda
arriba0x08salto vertical
arriba + derecha0x09salto a la derecha
arriba + izquierda0x0Asalto a la izquierda

Fíjate en los números: el bit 3 es "estoy saltando" y los bits 0 y 1 son la dirección. 0x09 es 0x08 | 0x01. Está pensado para poder consultar cada cosa por separado con una máscara.

La tabla de animación y física

Aquí está lo más elegante del juego. En 0x60C0 hay 50 ranuras de 128 bytes. Cada ranura es una secuencia de fotogramas, y cada fotograma ocupa 4 bytes:

byte 0:  desplazamiento en X (con signo)
byte 1:  desplazamiento en Y (con signo)
byte 2:  número de dibujo
byte 3:  los dos colores del sprite, uno en cada mitad del byte

Un 0x80 en el primer byte marca el final de la secuencia.

Lo interesante es que el movimiento y el dibujo son la misma tabla. La gravedad no es una fórmula: es una secuencia de fotogramas cuyos desplazamientos en Y van creciendo. El salto tampoco: es otra secuencia. Cambiar cómo se mueve el personaje es cambiar unos bytes de datos, no tocar código.

Las ranuras 0 a 13 son el monje, y de la 16 a la 48 los enemigos, emparejadas: la ranura N y la N+17 son el mismo bicho mirando al otro lado. Para que un enemigo se dé la vuelta, el juego suma o resta 17 al número de ranura.

Hay incluso una ranura (la 14) cuyos desplazamientos describen una circunferencia completa: es el enemigo que orbita.

Las pantallas

Cada pantalla es un bloque de 512 bytes desde 0x9000: 32 columnas × 16 filas, un byte de tile por casilla, sin comprimir. La cuenta es directa:

CARGA_MAPA:
    ld a,(08f0dh)   ; número de pantalla
    ld d,a
    sla d           ; DE = pantalla * 512
    ld e,000h
    ld hl,09000h
    add hl,de
    ld de,07d80h    ; al buffer de trabajo
    ld bc,00200h
    ldir

Son 29: las 28 jugables y la de victoria.

El tipo de cada casilla no está en el mapa: se deduce del número de tile. CLASIFICA_TILE los reparte en nueve clases (aire, pared, vida extra, munición, arma, terreno mortal…) comparando rangos.

Un caso curioso es la clase 1: un tile con dibujo vacío que el jugador atraviesa sin enterarse, pero que hace girar a los enemigos. Son paredes invisibles para acotar por dónde patrulla cada bicho. Hay 406 repartidas por los 28 mapas.

Los objetos ocultos

Cada pantalla tiene además una lista de hasta cuatro puntos ocultos, en 0x79C0 + pantalla*16, con cuatro bytes por punto:

columna, fila, disparos que aguanta, premio que suelta

El contador de disparos está guardado en negativo: el juego lo incrementa hasta que desborda a cero. Igual que la resistencia de los enemigos.

Lo llamativo, ya contado en EL-JUEGO.md, es que el sistema funciona por coordenadas: no mira qué hay dibujado. De ahí que 13 de los 30 puntos del juego sean invisibles.

El reproductor de sonido

Un motor de tres canales con formato propio, tipo tracker. Los datos son una secuencia de bytes donde los valores por debajo de 0x80 son notas y los de 0x80 en adelante son comandos:

ComandoQué hace
0x80volumen del canal
0x81mezclador (tono, ruido o ambos)
0x82volver al principio (bucle)
0x83duración de nota
0x84silencio
0x85tempo
0x86duración compuesta
0x87cargar instrumento
0x88periodo de ruido
0x89envolvente de ruido
0x8Arepetir envolventes
0x8Bfin del canal
0x8Cllamar a una frase
0x8Dvolver de la frase (cierra el 0x8C)
0x8Etransposición del canal en semitonos

Con instrumentos reutilizables y llamadas a frases, para no repetir datos. Todo en algo más de un kilobyte, y funcionando dentro de la interrupción de pantalla sin robarle tiempo al juego.

Las variables

Están todas juntas, de 0x8F00 a 0x8F9F. Las principales:

DirecciónQué es
0x8F09 / 0x8F0AX e Y del jugador, en píxeles
0x8F0Bestado / animación actual
0x8F0Cfotograma dentro de la animación
0x8F0Dpantalla (0–28)
0x8F0Enivel (0–3)
0x8F11modo flotar: alitas o nivel 4
0x8F12vidas
0x8F13 / 0x8F14dónde reaparecer al morir
0x8F17munición
0x8F18arma
0x8F1Ela bandera de tramposo que nadie enciende
0x8F20los 4 enemigos activos, 5 bytes cada uno
0x8F80los 7 disparos en vuelo, 4 bytes cada uno

La fuente

El juego no usa ASCII. Tiene su propia tabla:

espacio  0x00        (no 0x20)
A..Z     0x41..0x5A  (como en ASCII)
0..9     0x5C..0x65  (desplazados)
"        0x68
.        0x6A
,        0x6B
:        0x6C
-        0x6D

Por eso la rutina del marcador hace add 0x5C para convertir un número en su dígito.

Y una curiosidad: los códigos de la U y la V dibujan el mismo glifo, así que los textos guardados dicen cosas como "PVES" o "NUEUO" y en pantalla se leen perfectamente.