0x06 Ensamblador ZX Spectrum Marciano – Enemigos
En este capítulo de Ensamblador ZX Spectrum Marciano vamos a incluir los enemigos. Lo primero es crear la carpeta Paso06, copiamos desde la carpeta Paso05 los archivos Cargador.tap, Const.asm, Ctrl.asm, Game.asm, Graph.asm, Int.asm, Main.asm, Print.asm, Var.asm y make, o make.bat si estáis trabajando en Windows.
Tabla de contenidos
- Definimos los enemigos
- Pintamos los enemigos
- Movemos los enemigos
- Ensamblador ZX Spectrum, conclusión
- Enlaces de interés
Definimos los enemigos
Los enemigos son elementos móviles, y como tales necesitamos saber la posición actual y la inicial. En total vamos a tener un máximo de veinte enemigos en pantalla, y vamos a usar dos bytes para especificar la posición actual del enemigo y alguna configuración más que nos va a hacer falta.
Abrimos el archivo Var.asm y tras la definición del marco de la pantalla, añadimos la configuración de los enemigos.
; -----------------------------------------------------------------------------
; Configuración de los enemigos
;
; 2 bytes por enemigo.
; -----------------------------------------------------------------------------
; Byte 1 | Byte 2
; -----------------------------------------------------------------------------
; Bit 0-4: Posición Y | Bit 0-4: Posición X
; Bit 5: Libre | Bit 5: Libre
; Bit 6: Libre | Bit 6: Dirección X 0 = Left 1 = Right
; Bit 7: Activo 1/0 | Bit 7: Dirección Y 0 = Up 1 = Down
; -----------------------------------------------------------------------------
enemiesConfig:
db $96, $dd, $96, $d7, $96, $d1, $96, $cb, $96, $c5
db $93, $9d, $93, $97, $93, $91, $93, $8b, $93, $85
db $90, $dd, $90, $d7, $90, $d1, $90, $cb, $90, $c5
db $8d, $9d, $8d, $97, $8d, $91, $8d, $8b, $8d, $85
enemiesConfigIni:
db $96, $dd, $96, $d7, $96, $d1, $96, $cb, $96, $c5
db $93, $9d, $93, $97, $93, $91, $93, $8b, $93, $85
db $90, $dd, $90, $d7, $90, $d1, $90, $cb, $90, $c5
db $8d, $9d, $8d, $97, $8d, $91, $8d, $8b, $8d, $85
enemiesConfigEnd:
En el primer byte vamos a tener la coordenada Y, bits del cero al cuatro, y si el enemigo está o no activo, bit siete. En el segundo byte vamos a tener la coordenada X, bits del cero al cuatro, la dirección horizontal, bit seis, y la dirección vertical, bit siete.
Según los bits seis y siete del segundo byte, la dirección del enemigo es:
- 00b $00 Izquierda / Arriba
- 01b $01 Izquierda / Abajo
- 10b $02 Derecha / Arriba
- 11b $03 Derecha / Abajo
Basándonos en esto, vamos a añadir la definición de los gráficos de los enemigos. Justo antes de enemiesConfig añadimos dicha definición.
; -----------------------------------------------------------------------------
; Gráficos de los enemigos
;
; 00 Up-Left
; 01 Up-Right
; 10 Down-Left
; 11 Down-Right
; -----------------------------------------------------------------------------
enemiesGraph:
db $9f, $a0, $a1, $a2
Si buscamos los UDG de los enemigos, veremos que todo concuerda.
Volviendo a la definición de los enemigos, vamos a tener un total de veinte enemigos, repartidos en cuatro filas con cinco enemigos por fila.
Vamos a ver la definición de los enemigos de izquierda a derecha y de arriba a abajo; recordad que trabajamos con las coordenadas invertidas.
$96, $dd | 10010110, 11011101 | Activo Línea 22 Abajo/Derecha Columna 29 |
$96, $d7 | 10010110, 11010111 | Activo Línea 22 Abajo/Derecha Columna 23 |
$96, $d1 | 10010110, 11010001 | Activo Línea 22 Línea/Derecha Columna 17 |
$96, $cb | 10010110, 11001011 | Activo Línea 22 Abajo/Derecha Columna 11 |
$96, $c5 | 10010110, 11000101 | Activo Línea 22 Abajo/Derecha Columna 5 |
$93, $9d | 10010011, 10011101 | Activo Línea 19 Abajo/Izquierda Columna 29 |
$93, $97 | 10010011, 10010111 | Activo Línea 19 Abajo/Izquierda Columna 23 |
$93, $91 | 10010011, 10010001 | Activo Línea 19 Abajo/Izquierda Columna 17 |
$93, $8b | 10010011, 10001011 | Activo Línea 19 Abajo/Izquierda Columna 11 |
$93, $85 | 10010011, 10000101 | Activo Línea 19 Abajo/Izquierda Columna 5 |
$90, $dd | 10010000, 11011101 | Activo Línea 16 Abajo/Derecha Columna 29 |
$90, $d7 | 10010000, 11010111 | Activo Línea 16 Abajo/Derecha Columna 23 |
$90, $d1 | 10010000, 11010001 | Activo Línea 16 Abajo/Derecha Columna 17 |
$90, $cb | 10010000, 11001011 | Activo Línea 16 Abajo/Derecha Columna 11 |
$90, $c5 | 10010000, 11000101 | Activo Línea 16 Abajo/Derecha Columna 5 |
$8d, $9d | 10001101, 10011101 | Activo Línea 13 Abajo/Izquierda Columna 29 |
$8d, $97 | 10001101, 10010111 | Activo Línea 13 Abajo/Izquierda Columna 23 |
$8d, $91 | 10001101, 10010001 | Activo Línea 13 Abajo/Izquierda Columna 17 |
$8d, $8b | 10001101, 10001011 | Activo Línea 13 Abajo/Izquierda Columna 11 |
$8d, $85 | 10001101, 10000101 | Activo Línea 13 Abajo/Izquierda Columna 5 |
Una vez definidos los gráficos de los enemigos y su configuración, podemos proceder a pintarlos.
Pintamos los enemigos
La rutina que pinta los enemigos la vamos a implementar en Print.asm.
PrintEnemies:
ld a, $06
call Ink
ld hl, enemiesConfig
ld d, $14
Cargamos en A la tinta amarilla, LD A, $06, cambiamos la tinta, CALL Ink, cargamos la dirección de memoria de los enemigos en HL, LD HL, enemiesConfig, y el número total de enemigos en D, LD D, $14, veinte enemigos.
printEnemies_loop:
bit $07, (hl)
jr z, printEnemies_endLoop
Evaluamos si el enemigo está activo, BIT $07, (HL), y de no estarlo saltamos, JR Z, printEnemies_endLoop.
push hl
ld a, (hl)
and $1f
ld b, a
Preservamos el valor de HL, PUSH HL, cargamos el primer byte de la configuración del enemigo en A, LD A, (HL), nos quedamos con la coordenada Y, AND $1F, y la cargamos en B, LD B, A.
inc hl
ld a, (hl)
and $1f
ld c, a
call At
Apuntamos HL al segundo byte de la configuración del enemigo, INC HL, cargamos el valor en A, LD A, (HL), nos quedamos con la coordenada X, AND $1F, cargamos el valor en C, LD C, A, y posicionamos el cursor, CALL At.
ld a, (hl)
and $c0
rlca
rlca
ld c, a
ld b, $00
Cargamos el segundo byte de la configuración del enemigo en A, LD A, (HL), nos quedamos con los bits de dirección, AND $c0, pasamos el valor a los bits cero y uno, RLCA RLCA, cargamos el valor en C, LD C, A, y ponemos B a cero, LD B, $00.
ld hl, enemiesGraph
add hl, bc
ld a, (hl)
rst $10
Cargamos en HL la dirección de memoria en la que definimos los caracteres para los enemigos, LD HL, enemiesGraph, le sumamos la dirección (izquierda, arriba …) del enemigo, ADD HL, BC, cargamos en A el carácter del enemigo que hay que pintar, LD A, (HL), y lo pintamos, RST $10.
pop hl
printEnemies_endLoop:
inc hl
inc hl
dec d
jr nz, printEnemies_loop
ret
Recuperamos el valor de HL, POP HL, apuntamos HL al primer byte de la configuración del siguiente enemigo, INC HL INC HL, decrementamos D, DEC D, y seguimos hasta que D sea cero y hayamos recorrido todos los enemigos. Finalmente, salimos, RET.
El aspecto final de la rutina es el siguiente:
; -----------------------------------------------------------------------------
; Pinta los enemigos
;
; Altera el valor de los registros AF, BC, D y HL.
; -----------------------------------------------------------------------------
PrintEnemies:
ld a, $06 ; Carga en A la tinta amarilla
call Ink ; Cambia la tinta
ld hl, enemiesConfig ; Carga la dirección de la configuración
; del enemigo en HL
ld d, $14 ; Carga en D 20 enemigos
printEnemies_loop:
bit $07, (hl) ; Evalúa si el enemigo está activo
jr z, printEnemies_endLoop ; Si no lo está, salta
push hl ; Preserva el valor de HL
ld a, (hl) ; Carga el primer byte de configuración en A
and $1f ; Se queda con la coordenda Y
ld b, a ; La carga en B
inc hl ; Apunta HL al segundo byte
ld a, (hl) ; Carga el valor en A
and $1f ; Se queda con la coordenada X
ld c, a ; La carga en C
call At ; Posiciona el cursor
ld a, (hl) ; Vuelve a cargar el segudo byte en A
and $c0 ; Se queda con la dirección (izquierda ...)
rlca ; Pone el valor en los bits 0 y 1
rlca
ld c, a ; Carga el valor en C
ld b, $00 ; Pone B a cero
ld hl, enemiesGraph ; Carga en HL el carácter del gráfico del enemigo
add hl, bc ; Le suma la dirección de enemigo (izquierda ...)
ld a, (hl) ; Carga en A el gráfico del enemigo
rst $10 ; Lo pinta
pop hl ; Recupera el valor de HL
printEnemies_endLoop:
inc hl ; Apunta HL al primer byte de la configuración
inc hl ; del enemigo siguientes
dec d ; Decrementa D
jr nz, printEnemies_loop ; hasta que D sea 0
ret
Para probar si funciona, vamos a ir a Main.asm y justo antes de Main_loop vamos a añadir las líneas siguientes:
ld a, $01
call LoadUdgsEnemies
call PrintEnemies
Cargamos el nivel uno en A, LD A, $01, cargamos los gráficos de los enemigos del nivel uno en udgsExtension, CALL LoadUdgsEnemies, y pintamos los enemigos, CALL PrintEnemies.
Compilamos, cargamos en el emulador y vemos los resultados.
Movemos los enemigos
Los enemigos no los vamos a mover en cada iteración del bucle, como hacemos con el disparo, los vamos a mover cada N interrupciones, por lo qué lo primero que vamos a hacer es añadir otro comentario en la etiqueta flag, al inicio de Main.asm.
; Bit 2 -> se deben mover los enemigos 0 = No, 1 = Sí
Lo siguiente es establecer los límites de la pantalla hasta dónde los enemigos pueden llegar; estos límites los vamos a establecer en Const.asm.
; Topes de los enemigos
ENEMY_TOP_T: EQU COR_Y - MIN_Y
ENEMY_TOP_B: EQU COR_Y - MAX_Y + $01
ENEMY_TOP_L: EQU COR_X - MIN_X
ENEMY_TOP_R: EQU COR_X - MAX_X
Los límites que hemos establecido son arriba, abajo, izquierda y derecha.
Para que los enemigos se muevan cada N interrupciones, y como en flags vamos a usar un bit para indicar si se deben mover o no, vamos a ir al archivo Int.asm y vamos a activar dicho bit, para lo que añadimos la siguiente línea justo debajo de SET $00, (HL):
set $02, (hl)
Ya tenemos todos listo para poder implementar en Game.asm la rutina que mueve los enemigos.
MoveEnemies:
ld hl, flags
bit $02, (hl)
ret z
res $02, (hl)
Cargamos en HL la dirección de memoria de flags, LD HL, flags, comprobamos si el bit de movimiento de los enemigos está activo, BIT $02, (HL), y si no está activo, salimos, RET Z. En el caso de estar activo, lo desactivamos para que no pase por aquí en la próxima iteración de Main_loop, RES $02, (HL).
ld d, $14
ld hl, enemiesConfig
moveEnemies_loop:
bit $07, (hl)
jr z, moveEnemies_endLoop
Cargamos en D el número total de enemigos (veinte), LD D, $14, cargamos en HL la dirección de memoria de la configuración de los enemigos, LD HL, enemiesConfig, comprobamos si el enemigo está activo, BIT $07, (HL), y si no está activo saltamos al final del bucle, JR Z, moveEnemies_endLoop.
push hl
ld a, (hl)
and $1f
ld b, a
inc hl
ld a, (hl)
and $1f
ld c, a
call DeleteChar
pop hl
Preservamos el valor de HL, PUSH HL, cargamos en A el primer byte de la configuración del enemigo, LD A, (HL), nos quedamos con la coordenada Y, AND $1F, y la cargamos en B, LD B, A.
Apuntamos HL al segundo byte de la configuración del enemigo, INC HL, cargamos el valor en A, LD A, (HL), nos quedamos con la coordenada X, AND $1F, y la cargamos en C, LD C, A.
Borramos el enemigo, CALL DeleteChar, y recuperamos el valor de HL, POP HL.
ld b, (hl)
inc hl
ld c, (hl)
Cargamos el primer byte de la configuración del enemigo en B, LD B, (HL), apuntamos HL al segundo byte de la configuración, INC HL, y cargamos el valor en C, LD C, (HL).
moveEnemies_X:
ld a, c
and $1f
bit $06, c
jr nz, moveEnemies_X_right
Cargamos el valor del segundo byte de la configuración del enemigo en A, LD A, C, y nos quedamos con la coordenada X, AND $1F.
Comprobamos el bit de dirección horizontal del enemigo, BIT $06, C, si está a uno, el enemigo se desplaza hacia la derecha y salta, JR Z, moveEnemies_X_right. Si no ha saltado, el enemigo se mueve hacia la izquierda.
moveEnemies_X_left:
inc a
sub ENEMY_TOP_L
jr z, moveEnemies_X_leftChg
inc c
jr moveEnemies_Y
moveEnemies_X_leftChg:
set $06, c
jr moveEnemies_Y
Incrementamos A para que apunte a la columna a la izquierda de la actual, INC A, restamos el tope por la izquierda, SUB ENEMY_TOP_L, y si el resultado es cero, ha llegado al tope y salta para cambiar la dirección, JR Z, moveEnemies_X_leftChg.
Si no hay que cambiar la dirección, apunta C a la columna a la izquierda de la actual, INC C, y salta al movimiento vertical, JR moveEnemies_Y.
Si hay que cambiar la dirección, activa el bit seis de C para poner dirección horizontal hacia la derecha, SET $06, C, y salta al movimiento vertical, JR moveEnemies_Y.
Si el enemigo no se mueve hacia la izquierda, se mueve hacia la derecha.
moveEnemies_X_right:
dec a
sub ENEMY_TOP_R
jr z, moveEnemies_X_rightChg
dec c
jr moveEnemies_Y
moveEnemies_X_rightChg:
res $06, c
Decrementamos A para que apunte a la columna a la derecha de la actual, DEC A, restamos el tope por la derecha, SUB ENEMY_TOP_R, y si el resultado es cero, ha llegado al tope y salta para cambiar la dirección, JR Z, moveEnemies_X_rightChg.
Si no hay que cambiar la dirección, apunta C a la columna a la derecha de la actual, DEC C, y salta al movimiento vertical, JR moveEnemies_Y.
Si hay que cambiar la dirección, desactiva el bit seis de C para poner dirección horizontal hacia la izquierda, RES $06, C, y empezamos con el movimiento vertical.
moveEnemies_Y:
ld a, b
and $1f
bit $07, c
jr nz, moveEnemies_Y_down
Cargamos en A el valor del primer byte de la configuración del enemigo, LD A, B, y nos quedamos con la coordenada Y, AND $1F.
Comprobamos el bit siete de C para saber la dirección vertical del enemigo, BIT $07, C, y si está a uno el enemigo se mueve hacia abajo y salta, JR NZ, moveEnemies_Y_down.
Si el bit está a cero, el enemigo se mueve hacia arriba.
moveEnemies_Y_up:
inc a
sub ENEMY_TOP_T
jr z, moveEnemies_Y_upChg
inc b
jr moveEnemies_endMove
moveEnemies_Y_upChg:
set $07, c
jr moveEnemies_endMove
Incrementamos A para que apunte a la línea superior a la actual, INC A, restamos el tope por arriba, SUB ENEMY_TOP_T, y si el resultado es cero, hemos llegado al tope y saltamos para cambiar la dirección, JR Z, moveEnemies_Y_upChg.
Si no hemos llegado al tope, incrementamos B para que apunte a la línea superior a la actual, INC B, y saltamos al final del bucle, JR moveEnemies_endMove.
Si hay que cambiar la dirección, activamos el bit siete de C para cambiar la dirección hacia abajo, SET $07, C, y saltamos al final del bucle, JR moveEnemies_endMove.
Si el enemigo no se mueve hacia arriba, se mueve hacia abajo.
moveEnemies_Y_down:
dec a
sub ENEMY_TOP_B
jr z, moveEnemies_Y_downChg
dec b
jr moveEnemies_endMove
moveEnemies_Y_downChg:
res $07, c
Decrementamos A para que apunte a la línea inferior a la actual, DEC A, restamos el tope por abajo, SUB ENEMY_TOP_B, y si el resultado es cero, hemos llegado al tope y saltamos para cambiar la dirección, JR Z, moveEnemies_Y_downChg.
Si no hemos llegado al tope, decrementamos B para que apunte a la línea inferior a la actual, DEC B, y saltamos al final del bucle, JR moveEnemies_endMove.
Si hay que cambiar la dirección, desactivamos el bit siete de C para cambiar la dirección hacia arriba, RES $07, C.
moveEnemies_endMove:
ld (hl), c
dec hl
ld (hl), b
moveEnemies_endLoop:
inc hl
inc hl
dec d
jr nz, moveEnemies_loop
Actualizamos en memoria el segundo byte de la configuración del enemigo, LD (HL), C, apuntamos HL al primer byte, DEC HL, y actualizamos en memoria, LD (HL), B.
Apuntamos HL al primer byte de la configuración del siguiente enemigo, INC HL, INC HL, decrementamos D, DEC D, y seguimos en el bucle hasta que D sea cero y hayamos recorrido los veinte enemigos, JR Z, moveEnemies_loop.
moveEnemies_end:
call PrintEnemies
ret
Pintamos los enemigos en las nuevas posiciones, CALL PrintEnemies, y salimos, RET.
Con esto, hemos implementado la rutina que mueve los enemigos, cuyo aspecto final es el siguiente:
; -----------------------------------------------------------------------------
; Mueve los enemigos.
;
; Altera el valor de lo registros AF, BC, D y HL.
; -----------------------------------------------------------------------------
MoveEnemies:
ld hl, flags ; Cargamos la dirección de memoria de flags en HL
bit $02, (hl) ; Comprueba si el bit 2 está activo
ret z ; Si no es así, sale
res $02, (hl) ; Desactiva el bit 2 de flags
ld d, $14 ; Carga en D el número total de enemigos (20)
ld hl, enemiesConfig ; Cara en HL la dirección de la configuración
; de los enemigos
moveEnemies_loop:
bit $07, (hl) ; Comprueba si el enemigo está activo
jr z, moveEnemies_endLoop ; Si no lo está, salta a final del bucle
push hl ; Preserva el valor de HL
ld a, (hl) ; Carga en A el primer byte de la cofiguración
and $1f ; Se queda con la coordenada Y
ld b, a ; Carga el valor en B
inc hl ; Apunta HL al segundo byte de la configuración
ld a, (hl) ; Carga el valor en A
and $1f ; Se queda con la coordeanda X
ld c, a ; Carga el valor en C
call DeleteChar ; Borra el enemigo
pop hl ; Recupera el valor de HL
ld b, (hl) ; Carga en B el primer byte de la configuración
inc hl ; Apunta HL al segundo byte de la configuración
ld c, (hl) ; Carga en C el segundo byte de la configuración
moveEnemies_X:
ld a, c ; Carga en A el segundo byte de la configuración
and $1f ; Se queda con la coordeada X
bit $06, c ; Evalúa la dirección horizontal del enemigo
jr nz, moveEnemies_X_right ; Si está a uno, hacia la derecha, salta
moveEnemies_X_left:
inc a ; Apunta A a la columna anterior
sub ENEMY_TOP_L ; Resta el tope por la izquierda
jr z, moveEnemies_X_leftChg ; Si es cero, ha llegado al tope, salta
inc c ; Apunta C a la columna anterior
jr moveEnemies_Y ; Salta al movimiento vertical
moveEnemies_X_leftChg:
set $06, c ; Pone la dirección horizontal hacia la derecha
jr moveEnemies_Y ; Salta al movimiento vertical
moveEnemies_X_right:
dec a ; Apunta A a la columna posterior
sub ENEMY_TOP_R ; Resta el tope por la derecha
jr z, moveEnemies_X_rightChg ; Si es cero, ha llegado al tope, salta
dec c ; Apunta C a la columna posterior
jr moveEnemies_Y ; Salta al movimiento vertical
moveEnemies_X_rightChg:
res $06, c ; Pone la dirección horizontal hacia la izquierda
moveEnemies_Y:
ld a, b ; Carga en A el primer byte de la configuración
and $1f ; Se qued con la coordenda Y
bit $07, c ; Evalúa la dirección vertical del enemigo
jr nz, moveEnemies_Y_down ; Si está a uno, hacia abajo, salta
moveEnemies_Y_up:
inc a ; Apunta A a la línea anterior
sub ENEMY_TOP_T ; Resta el tope por arriba
jr z, moveEnemies_Y_upChg ; Si es cero, ha llegado al tope, salta
inc b ; Apunta B a la línea posterior
jr moveEnemies_endMove ; Salta al final del bucle
moveEnemies_Y_upChg:
set $07, c ; Pone la dirección vertical hacia abajo
jr moveEnemies_endMove ; Salta al final del bucle
moveEnemies_Y_down:
dec a ; Apunta A a la línea posterior
sub ENEMY_TOP_B ; Resta el tope por abajo
jr z, moveEnemies_Y_downChg ; Si es cero, ha llegado al tope, salta
dec b ; Apunta B a la línea posterior
jr moveEnemies_endMove ; Salta al final del bucle
moveEnemies_Y_downChg:
res $07, c ; Pone la dirección vertical hacia arriba
moveEnemies_endMove:
ld (hl), c ; Actualiza el segundo byte de la configuración
dec hl ; Apunta HL al primer byte de la configuración
ld (hl), b ; Actualiza el primer byte de la configuración
moveEnemies_endLoop
inc hl
inc hl ; Aputa HL al primer byte de la configuración
; del siguiente enemigo
dec d ; Decrementa D
jr nz, moveEnemies_loop ; Hasta que D sea cero (20 enemigos)
moveEnemies_end:
call PrintEnemies ; Pinta los enemigos
ret
Ha llegado el momento de ver como se mueven los enemigos. Abrimos el archivo Main.asm y en la etiqueta Main_loop, justo debajo de CALL MoveShip, añadimos la línea siguiente:
call MoveEnemies
Compilamos, cargamos en el emulador y vemos los resultados.
¿Qué tal? ¿Se mueven los enemigos? Sí, se mueven, pero van demasiado deprisa y el disparo se ha vuelto más lento. Vamos a ralentizar el movimiento de los enemigos, y lo vamos hacer desde la rutina de la interrupciones, así que vamos al archivo Int.asm.
Vamos a hacer algo parecido a lo que hicimos en PorompomPong, vamos a añadir un contador al final del archivo para controlar cuando activamos el movimiento de los enemigos.
countEnemy: db $00
Ahora entre las líneas SET $00, (HL) y SET $02, (HL) vamos a implementar el uso de este contador.
ld a, (countEnemy)
inc a
ld (countEnemy), a
sub $03
jr nz, Isr_end
ld (countEnemy), a
Cargamos en A el valor del contador, LD A, (countEnemy), incrementamos A, INC A, y actualizamos el contador en memoria, LD (countEnemy), A. Restamos a A el valor que tiene que alcanzar el contador para activar el movimiento, SUB $03, y si no ha llegado salta al final de la rutina, JR NZ, Isr_end.
Si ya ha alcanzado el valor, pone a cero el contador, LD (countEnemy), A, y activa el bit para mover los enemigos, SET $02, (HL).
Compilamos, cargamos en el emulador y vemos que hemos recuperado la velocidad del disparo y que los enemigos se siguen moviendo rápido.
Ensamblador ZX Spectrum, conclusión
Ya tenemos en movimiento todos los elementos de nuestro juego.
En el siguiente capítulo implementaremos la detección de colisiones, para poder matar a los enemigos y que ellos nos maten a nosotros, y poder ir cambiando de nivel, hasta un total de treinta.
Podéis descargar todo el código generado desde aquí.
Enlaces de interés
- Notepad++
- Visual Studio Code
- Sublime Text
- ZEsarUX
- PASMO
- Git
- Curso de ensamblador Z80 de Compiler Software
- Referencia Z80
- Ensamblador Z80 en Telegram
- Tutorial completo en formato PDF y EPUB
- Proyecto en itch.io
- Archivo .dsk con los juegos de los tutoriales
- Personalización y depuración con ZEsarUX
Ensamblador para ZX Spectrum Batalla espacial por Juan Antonio Rubio García.
Esta obra está bajo licencia de Creative Commons Reconocimiento-NoComercial-CompartirIgual 4.0 Internacional License.
También puedes visitar el resto de tutoriales:
Y recuerda, si lo usas no te limites a copiarlo, intenta entenderlo y adaptarlo a tus necesidades.