0x09 Ensamblador ZX Spectrum Pong – Cambio de dirección/velocidad de la bola al golpear la pala

En esta nueva entrega de Ensamblador ZX Spectrum Pong, vamos a prescindir de parte de lo que hemos implementado en la entrega anterior. La velocidad de la bola va a cambiar dependiendo de con qué parte de la pala colisione.

Ensamblador ZX Spectrum – Cambio de dirección/velocidad de la bola al golpear la pala

Creamos la carpeta Paso09 y copiamos los archivos Controls.asm, Game.asm, Main.asm, Sprite.asm y Video.asm desde la carpeta Paso08.

Lo primero que vamos a hacer es quitar la posibilidad de cambiar la velocidad de la bola con las teclas del 1 al 3.

Abrimos el archivo Controls.asm y en la rutina ScanKeys, borramos todas las líneas hasta la etiqueta scanKeys_ctrl, quedando el inicio de la rutina de la siguiente manera.

ScanKeys:
ld		d, $00

scanKeys_A:

Si compilamos y cargamos en el emulador, vemos que la velocidad de la bola no cambia.

Vamos a añadir nuevas constantes y variables en el archivo Sprite.asm, para poder controlar la inclinación de la bola. También vamos a cambiar los sprites de las palas; ambas van a dibujar cuatro píxeles, pero en ambos casos dibujaremos los más cercanos al centro de la pantalla.

Añadimos las constantes que indican la rotación a asignar a la bola cuando se produce la colisión con la pala.

CROSS_LEFT_ROT: 	EQU	$ff
CROSS_RIGHT_ROT:	EQU	$01

Añadimos la posición inicial de la bola, y el número acumulado de movimientos que debe llevar la bola para cambiar la posición Y. Este último dato lo vamos a usar para cambiar la inclinación de la bola.

BALLPOS_INI:	EQU	$4850
ballMovCount:	db	$00

Cambiamos la configuración inicial de la bola y la documentación (comentarios) de la misma.

; Velocidad y dirección de la bola.
; bits 0 a 3: 	Movimientos de la bola para que cambie la posición Y. 
;				Valores f = semiplano, 2 = semi diagonal, 1 = diagonal
; bits 4 y 5:	Velocidad de la bola: 1 muy rápido, 2 (rápido) y 3 (lento)
; bit 6: 		Dirección X: 0 derecha / 1 izquierda
; bit 7:		Dirección Y: 0 arriba / 1 abajo
ballSetting:
db		$31		; 0011 0001

Según la nueva configuración, la bola inicialmente se mueve hacia la derecha y hacia arriba, con una velocidad lenta, y en cada movimiento cambia la posición Y (va en diagonal).

Añadimos distintos sprites para las palas y eliminamos el anterior.

PADDLE:		EQU	$3c		; ¡ELIMINAR!
PADDLE1:	EQU	$0f		; ¡NUEVO!
PADDLE2:	EQU	$f0		; ¡NUEVO!

Por último, añadimos las posiciones iniciales de las palas.

PADDLE1POS_INI:	EQU     $4861
PADDLE2POS_INI:	EQU     $487e

Hemos añadido sprites distintos para cada pala y eliminado la constante que usábamos para pintar las palas; si compilamos, nos dará errores. Vamos a solucionar esos errores modificando la rutina PrintPaddle de Video.asm.

La rutina PrintPaddle recibe en el registro HL la posición de la pala. En el registro C recibirá el sprite de la pala.

Modificamos la línea justo debajo de la etiqueta printPaddle_loop.

ld (hl), PADDLE

Y la dejamos como sigue.

ld		(hl), c

Compilamos, y aunque no da ningún error, al cargar en el emulador vemos que los resultados no son los deseados.

Ensamblador ZX Spectrum - No pinta bien las palas
Ensamblador ZX Spectrum, no pinta bien las palas

La pala que pinta no se corresponde con el sprite que hemos definido, esto es debido a que no hemos cargado en C el sprite que debe pintar.

Abrimos el archivo Main.asm, y buscamos la etiqueta loop_continue. A partir de la línea 5 es donde imprimimos las palas, cargando el HL la posición de la pala y llamando al pintado de la misma. Antes de llamar al pintado de la pala, debemos especificar qué sprite debe pintar.

Este es el aspecto una vez hecha la modificación.

ld		hl, (paddle1pos)
ld		c, PADDLE1			; ¡NUEVO!
call	PrintPaddle
ld		hl, (paddle2pos)
ld		c, PADDLE2			; ¡NUEVO!
call	PrintPaddle

Compilamos, abrimos en el emulador, y comprobamos que las palas se vuelven a pintar bien.

Ensamblador ZX Spectrum - Pinta bien las palas
Ensamblador ZX Spectrum, pinta bien las palas

Aprovechando que estamos en Main.asm, vamos a cambiar un comportamiento del que quizá no os habéis percatado. Cuando se acaba un partido, y al iniciar otro, las palas siguen en la misma posición donde estaban al acabar el partido anterior, y la bola sale desde el campo del jugador que anotó el último punto.

Para modificar este comportamiento, vamos a añadir las siguientes líneas antes de la etiqueta Loop.

ld		hl, BALLPOS_INI
ld		(ballPos), hl
ld		hl, PADDLE1POS_INI
ld		(paddle1pos), hl
ld		hl, PADDLE2POS_INI
ld		(paddle2pos), hl

Con estas líneas situamos la bola y las palas en sus posiciones iniciales.

Si compilamos, vemos que nos da un error.

ERROR on line 68 of file Main.asm
ERROR: Relative jump out of range

Este error es debido a que, al ir añadiendo líneas, tenemos algún JR que está fuera de rango. JR solo puede saltar 127 bytes hacia adelante o 128 hacia atrás, y tenemos algún JR que salta a alguna dirección fuera de este rango. En concreto, tenemos al final del archivo Main.asm, dos JR Main y un JR Loop. Sustituimos estos tres JR por JP, y solucionamos el error. JP ocupa un byte más que JR, por lo que nuestro programa acaba de crecer 3 bytes, pero hemos reducido 6 ciclos de reloj.

Compilamos, cargamos en el emulador y comprobamos que, al acabar la partida e iniciar otra, tanto la bola como las palas vuelven a su posición inicial.

Cambio de velocidad, inclinación y dirección

Vamos a implementar el cambio de velocidad, inclinación y dirección de la bola al colisionar con las palas.

Abrimos el archivo Game.asm y buscamos la etiqueta checkBallCross_left. Tres líneas por encima encontramos.

ld a, $ff

Modificamos esta línea y la dejamos como sigue.

ld		a, CROSS_LEFT_ROT

Buscamos la etiqueta CheckCrossX. Tres líneas por encima encontramos.

ld a, $01

Modificamos esta línea y la dejamos como sigue.

ld		a, CROSS_RIGHT_ROT

Hemos cambiado los valores por constantes, para si en un futuro hay que cambiar los valores tenerlos mejor localizados.

El siguiente paso es cambiar la configuración de la bola, dependiendo de en qué parte de la pala colisiona. Vamos a dividir la pala en 5 partes, dependiendo de dónde colisione la bola el comportamiento será como sigue.

Zona de golpeoDirección verticalInclinaciónVelocidad
1/5ArribaDiagonal3 lento
2/5ArribaSemi diagonal2 normal
3/5No cambiaSemi plano1 rápido
4/5AbajoSemi diagonal2 normal
5/5AbajoDiagonal3 lento
Ensamblador ZX Spectrum, comportamiento de la bola

Localizamos la etiqueta CheckCrossY, nos vamos a la penúltima línea, XOR A, e implementamos justo antes de ella.

ld		a, c	
sub		$15
ld		c, a	
ld		a, b
add		a, $04
ld		b, a

Cuando llegamos a este punto, en C tenemos la posición del penúltimo scanline de la pala, y en B la posición de la bola. Ambas posiciones están en formato TTLLLSSS.

Cargamos en A la posición del penúltimo scanline de la pala, LD A, C, nos posicionamos en el primero, SUB $15, y volvemos a cargar el valor en C, LD C, A. Cargamos en A la posición de la bola, LD A, B, nos posicionamos en la parte baja de la bola, ADD A, $04, y volvemos a cargar el valor en B, LD B, A.

A partir de aquí implementamos el cambio de comportamiento, dependiendo del lugar de colisión de la bola.

checkCrossY_1_5:
ld		a, c
add		a, $04
cp		b
jr		c, checkCrossY_2_5

Cargamos la posición vertical de la pala en A, LD A, C, nos posicionamos en el último scanline de la primera parte, ADD A, $04, y lo comparamos con la posición de la bola, CP B. Si hay acarreo, la bola está más abajo y salta a comprobar la siguiente parte, JR C, checkCrossY_2_5.

Si no hay acarreo, la bola ha colisionado en esta parte y tenemos que cambiar su configuración.

ld		a, (ballSetting)	
and		$40
or		$31
jr		checkCrossY_end

Cargamos la configuración de la bola en A, LD A, (ballSetting), nos quedamos con la dirección horizontal (ya viene calculada), AND $40, y ponemos dirección vertical hacia arriba, velocidad 3 e inclinación diagonal, OR $31. Saltamos al final de la rutina, JR checkCrossY_end.

Si la bola no ha colisionado con la primera parte de la pala, comprobamos si lo ha hecho con la segunda.

checkCrossY_2_5:
ld		a, c
add		a, $09
cp		b
jr		c, checkCrossY_3_5

Cargamos la posición vertical de la pala en A, LD A, C, nos posicionamos en el último scanline de la segunda parte, ADD A, $09, y lo comparamos con la posición de la bola, CP B. Si hay acarreo, la bola está más abajo y salta a comprobar la siguiente parte, JR C, checkCrossY_3_5.

Si no hay acarreo, la bola ha colisionado en esta parte y tenemos que cambiar su configuración.

ld		a, (ballSetting)
and		$40
or		$22
jr		checkCrossY_end

Cargamos la configuración de la bola en A, LD A, (ballSetting), nos quedamos con la dirección horizontal (ya viene calculada), AND $40, y ponemos dirección vertical hacia arriba, velocidad 2 e inclinación semi diagonal, OR $22. Saltamos al final de la rutina, JR checkCrossY_end.

Si la bola no ha colisionado con la segunda parte de la pala, comprobamos si lo ha hecho con la tercera.

checkCrossY_3_5:
ld		a, c
add		a, $0d
cp		b
jr		c, checkCrossY_4_5

Cargamos la posición vertical de la pala en A, LD A, C, nos posicionamos en el último scanline de la tercera parte, ADD A, $0D, y lo comparamos con la posición de la bola, CP B. Si hay acarreo, la bola está más abajo y salta a comprobar la siguiente parte, JR C, checkCrossY_4_5.

Si no hay acarreo, la bola ha colisionado en esta parte y tenemos que cambiar su configuración.

ld		a, (ballSetting)	
and		$c0	
or		$1f	
jr		checkCrossY_end

Cargamos la configuración de la bola en A, LD A, (ballSetting), nos quedamos con la dirección horizontal y con la vertical (ya vienen calculadas), AND $C0, y ponemos velocidad 1 e inclinación semi plana, OR $1F. Saltamos al final de la rutina, JR checkCrossY_end.

Si la bola no ha colisionado con la tercera parte de la pala, comprobamos si lo ha hecho con la cuarta.

checkCrossY_4_5:
ld		a, c
add		a, $11
cp		b
jr		c, checkCrossY_5_5

Cargamos la posición vertical de la pala en A, LD A, C, nos posicionamos en el último scanline de la cuarta parte, ADD A, $11, y lo comparamos con la posición de la bola, CP B. Si hay acarreo, la bola está más abajo y salta a comprobar la siguiente parte, JR C, checkCrossY_5_5.

Si no hay acarreo, la bola ha colisionado en esta parte y tenemos que cambiar su configuración.

ld		a, (ballSetting)
and		$40
or		$a2
jr		checkCrossY_end

Cargamos la configuración de la bola en A, LD A, (ballSetting), nos quedamos con la dirección horizontal (ya viene calculada), AND $40, y ponemos dirección vertical hacia abajo, velocidad 2 e inclinación semi diagonal, OR $A2. Saltamos al final de la rutina, JR checkCrossY_end.

Si la bola no ha colisionado con la cuarta parte de la pala, lo ha hecho con la quinta.

checkCrossY_5_5:
ld		a, (ballSetting)	
and		$40
or		$b1

Cargamos la configuración de la bola en A, LD A, (ballSetting), nos quedamos con la dirección horizontal (ya viene calculada), AND $40, y ponemos dirección vertical hacia abajo, velocidad 3 e inclinación diagonal, OR $B1.

Por último, justo por encima de XOR A, vamos a añadir la etiqueta de fin de función a la que hemos estado haciendo referencia, y vamos a cargar la nueva configuración de la bola en memoria.

checkCrossY_end:
ld		(ballSetting), a

Después de XOR A, vamos a poner el contador de movimientos de la bola a 0.

ld		(ballMovCount), a

El aspecto final de la rutina es el siguiente.

; -----------------------------------------------------------------------------
; Evalúa si la bola colisiona en el eje Y con la pala.
; En el caso de colisionar, actualiza la configuración de la bola.
; Entrada:	HL = Posición de la pala	
; Salida:	Z = Colisiona.
;			NZ = No colisiona.
; Altera el valor de los registros AF, BC y HL.
; -----------------------------------------------------------------------------
CheckCrossY:
call	GetPtrY				; Obtiene la posición vertical de la pala (TTLLLSSS)
; La posición devuelta apunta al primer scanline de la pala que está a 0
; apunta al siguiente
inc		a
ld		c, a				; Carga el valor en C
ld		hl, (ballPos)		; Carga en HL la posición de la bola
call	GetPtrY				; Obtiene la posición vertical de la bola (TTLLLSSS)
ld		b, a				; Carga el valor en B
; Comprueba si la bola pasa por encima de la pala
; La bola está compuesta de 1 scanline a 0, 4 a $3c y otro a 0
; La posición apunta al 1er scanline, y se comprueba la colisión con el 5º
add		a, $04				; Apunta la posición de la bola al 5º scanline
sub		c					; Resta a la posición de la bola, la posición de la pala
ret		c					; Si hay acarreo sale porque la bola pasa por encima
; Comprueba si la bola pasa por debajo de la pala
ld		a, c				; Carga la posición vertical de la pala en A
add		a, $16				; Le suma 22 para apuntar al penúltimo scanline, 
 							; último que no es 0
ld		c, a				; Lo vuelve a cargar en C
ld		a, b				; Carga la posición vertical de la bola
inc		a					; Le suma 1 para apuntar el scanline 1, primero que no es 0
sub		c					; Resta a la posición de la bola, la posición de la pala
ret		nc					; Si no hay acarreo la bola pasa por debajo 
 							; de la pala o colisiona en el último scanline.
 							; En este último caso se activa el flag Z

; Dependiendo de donde sea la colisión, se asigna grado de inclinación
; y velocidad a la bola
ld		a, c				; Carga la posición del penúltimo scanline de la pala en A
sub		$15					; Lo vuelve a posicionar en el primero
ld		c, a				; Carga el valor en C

ld		a, b				; Carga en A la posición de la bola
add		a, $04				; Se posiciona en la parte baja de la bola
ld		b, a				; Carga el valor en B

checkCrossY_1_5:
ld		a, c				; Carga la posición vertical de la pala en A
add		a, $04				; Se posiciona en el último scanline de 1/5
cp		b					; Lo compara con la posición de la bola
jr		c, checkCrossY_2_5 	; La bola está más abajo, salta
ld		a, (ballSetting)	; Carga la configuración de la bola en A
and		$40					; Se queda con la dirección horizontal
or		$31					; Hacia arriba, velocidad 3 e inclinación diagonal
jr		checkCrossY_end		; Fin de la rutina

checkCrossY_2_5:
ld		a, c				; Carga la posición vertical de la pala en A
add		a, $09				; Se posiciona en el último byte de 2/5
cp		b					; Lo compara con la posición de la bola
jr		c, checkCrossY_3_5 	; La bola está más abajo, salta
ld		a, (ballSetting)	; Carga la configuración de la bola en A
and		$40					; Se queda con la dirección horizontal
or		$22					; Hacia arriba, velocidad 2 e inclinación semi diagonal
jr		checkCrossY_end		; Fin de la rutina

checkCrossY_3_5:
ld		a, c				; Carga la posición vertical de la pala en A
add		a, $0d				; Se posiciona en el último byte de 3/5
cp		b					; Lo compara con la posición de la bola
jr		c, checkCrossY_4_5 	; La bola está más abajo, salta
ld		a, (ballSetting)	; Carga la configuración de la bola en A
and		$c0					; Se queda con la dirección horizontal y vertical
or		$1f					; Hacia arriba/abajo, velocidad 1 e inclinación semi plano
jr		checkCrossY_end		; Fin de la rutina

checkCrossY_4_5:
ld		a, c				; Carga la posición vertical de la pala en A
add		a, $11				; Se posiciona en el último byte de 4/5
cp		b					; Lo compara con la posición de la bola
jr		c, checkCrossY_5_5 	; La bola está más abajo, salta
ld		a, (ballSetting)	; Carga la configuración de la bola en A
and		$40					; Se queda con la dirección horizontal y vertical
or		$a2					; Hacia abajo, velocidad 2 e inclinación semi diagonal
jr		checkCrossY_end		; Fin de la rutina

checkCrossY_5_5:
ld		a, (ballSetting)	; Carga la configuración de la bola en A
and		$40					; Se queda con la dirección horizontal
or		$b1 				; Hacia abajo, velocidad 3 e inclinación diagonal

; Hay colisión
checkCrossY_end:
ld		(ballSetting), a	; Carga en memoria la configuración actual de la bola
xor		a					; Activa el flag Z y pone A = 0
ld		(ballMovCount), a	; Pone el contador de movimientos de la bola a 0
ret

Compilamos, cargamos en el emulador y vemos los resultados.

Vemos que la velocidad sí cambia dependiendo de dónde colisiona la bola, pero no la inclinación. Además, al marcar un tanto, la velocidad no se reinicia, lo cual hace que sea muy difícil seguir jugando si la bola va a la velocidad máxima.

¿Por qué cambia la velocidad, pero no la inclinación?

Si hacemos memoria, en el paso anterior implementamos la posibilidad de cambiar la velocidad de la bola con las teclas del 1 al 3. De hecho, este paso lo iniciamos avisando de que íbamos a prescindir de esta implementación, pero de lo que no se ha prescindido es del cambio que hicimos en Main.asm para tener en cuenta la velocidad de la bola que marque la configuración; por eso la velocidad cambia.

Nos falta la implementación para tener en cuenta la inclinación, y para que cuando se marca un punto, velocidad e inclinación de la bola se reinicien.

Vamos a empezar con el cambio de inclinación. Seguimos en el archivo Game.asm, implementando la rutina que va a cambiar la posición Y de la bola. La vamos a implementar después del RET de la etiqueta moveBall_end.

MoveBallY:
ld		a, (ballSetting)
and		$0f
ld		d, a

Cargamos en A la configuración de la bola, LD A, (ballSetting), nos quedamos con la inclinación, AND $0F, y cargamos el valor en D, LD A, D.

ld		a, (ballMovCount)
inc		a
ld		(ballMovCount), a
cp		d
ret		nz

Cargamos los movimientos de la bola en A, LD A, (ballMovCount), lo incrementamos en 1, INC A, cargamos el valor en memoria, LD (ballMovCount), A, y lo comparamos con D, que contiene el número de movimientos necesarios para cambiar la posición Y de la bola, CP D. Si no son iguales, no se ha llegado al valor necesario y salimos, RET NZ.

xor		a
ld		(ballMovCount), a
ret

Si hemos llegado al valor, ponemos A = 0 y activamos el flag Z, XOR A, ponemos a 0 los movimientos acumulados de la bola, LD (ballMovCount), A, y salimos, RET. Al activar el flag Z se indica, a quien llame, que se debe cambiar la posición Y de la bola.

El aspecto final de la rutina es el siguiente.

; -----------------------------------------------------------------------------
; Cambia la posición Y de la bola
; Altera el valor de los registros AF y D.
; -----------------------------------------------------------------------------
MoveBallY:
ld		a, (ballSetting)	; Carga en A la configuración de la bola
and		$0f					; Se queda con la inclinación
ld		d, a				; Carga el valor en D

ld		a, (ballMovCount)	; Carga en A los movimientos acumulados de la bola
inc		a					; Incrementa A
ld		(ballMovCount), a	; Carga el valor en memoria
cp		d					; Lo compara con la inclinación
ret		nz					; Si no son iguales, sale. No se cambia la posición

; La posición debe cambiar
xor		a					; Pone A = 0 y activa el flag Z
ld		(ballMovCount), a	; Pone los movimientos acumulados de la bola a 0

ret

Localizamos la etiqueta moveBall_up, y entre las líneas JR Z, moveBall_upChg y CALL PreviousScan, añadimos las siguientes líneas.

call	MoveBallY
jr		nz, moveBall_x

Evaluamos si se tiene que cambiar la posición Y de la bola, CALL MoveBallY, y de no ser así salta, JR NZ, moveBall_x.

Localizamos la etiqueta moveBall_down, y entre las líneas JR Z, moveBall_downChg y CALL NextScan, añadimos las siguientes líneas.

call	MoveBallY
jr		nz, moveBall_x

Evaluamos si se tiene que cambiar la posición Y de la bola, CALL MoveBallY, y de no ser así salta, JR NZ, moveBall_x.

Compilamos, cargamos en el emulador, y comprobamos que ahora cambian la inclinación y la velocidad.

Por último, vamos a hacer que cuando se marque un punto, se reinicien la velocidad y la inclinación de la bola.

Localizamos la rutina SetBallLeft, eliminamos la línea AND $BF, y la sustituimos por las siguientes.

and 	$80
or		$31

Se queda con la dirección Y, AND $80, y pone dirección horizontal hacia la derecha, velocidad 3 e inclinación diagonal, OR $31.

Antes de la instrucción RET, añadimos las siguientes líneas.

ld		a, $00
ld		(ballMovCount), a

Ponemos A = 0, LD A, $00, y ponemos los movimientos de la bola a 0, LD (ballMovCount), A.

Localizamos la rutina SetBallRight y eliminamos la línea OR $40 y la sustituimos por las siguientes.

and		$80
or		$71

Se queda con la dirección Y, AND $80, y pone dirección horizontal hacia la izquierda, velocidad 3 e inclinación diagonal, OR $11.

Antes de la instrucción RET, añadimos las siguientes líneas.

ld		a, $00
ld		(ballMovCount), a

Ponemos A = 0, LD A, $00, y ponemos los movimientos de la bola a 0, LD (ballMovCount), A.

El aspecto final de ambas rutinas es el siguiente.

; -----------------------------------------------------------------------------
; Posiciona la bola a la izquierda.
; Altera el valor de los registros AF y HL.
; -----------------------------------------------------------------------------
SetBallLeft:
ld		hl, $4d60			; Carga en HL la posición de la bola
ld		(ballPos), hl		; Carga el valor en memoria
ld		a, $01				; Carga 1 en A
ld		(ballRotation), a	; Lo carga en memoria Rotación = 1
ld		a, (ballSetting)	; Carga en A la dirección y velocidad de la bola
and 	$80					; Se queda con la dirección Y
or		$31					; Pone dirección X a derecha, velocidad 3
 							; e inclinación diagonal
ld		(ballSetting), a	; Carga la nueva dirección de la bola en memoria
ld		a, $00
ld		(ballMovCount), a

ret

; -----------------------------------------------------------------------------
; Posiciona la bola a la derecha.
; Altera el valor de los registros AF y HL.
; -----------------------------------------------------------------------------
SetBallRight:
ld		hl, $4d7e			; Carga en HL la posición de la bola
ld		(ballPos), hl		; Carga el valor en memoria
ld		a, $ff				; Carga -1 en A
ld		(ballRotation), a	; Lo carga en memoria Rotación = -1
ld		a, (ballSetting)	; Carga en A la dirección y velocidad de la bola
and		$80					; Se queda con la dirección Y
or		$71					; Pone dirección X a izquierda, velocidad 3 
 							; e inclinación diagonal
ld		(ballSetting), a	; Carga la nueva dirección de la bola en memoria
ld		a, $00			
ld		(ballMovCount), a

ret

Compilamos, cargamos en el emulador y vemos los resultados, que deben ser los esperados, aunque la bola va algo lenta, ¿o no?

¿Os habéis fijado que cuando la bola golpea en la parte más baja de la pala no cambia ni dirección vertical, ni inclinación, ni velocidad? ¿Sabéis a qué se debe? Al final del tutorial veremos el por qué.

Podéis descargar todo el código que hemos generado.

Enlaces de interés

Vídeo

Si lo prefieres, puedes ver el vídeo que grabamos de esta sesión.

Ensamblador para ZX Spectrum PONG por Juan Antonio Rubio García.
Comentarios al código por Spirax.
Correcciones al texto original realizadas por Joaquín Ferrero.
Esta obra está bajo licencia de Creative Commons Reconocimiento-NoComercial-CompartitIgual 4.0 Internacional License.
Este tutorial ha sido publicado con anterioridad en AUA y se han grabado vídeos que están publicados a través de Retro Parla.

No olvides visitar las webs amigas.

AUA

Aquí puedes ver más cosas que he desarrollado para .Net, y aquí las desarrolladas en ensamblador para Z80.

Y recuerda, si lo usas no te limites a copiarlo, intenta entenderlo y adaptarlo a tus necesidades.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

Este sitio web utiliza cookies para que usted tenga la mejor experiencia de usuario. Si continúa navegando está dando su consentimiento para la aceptación de las mencionadas cookies y la aceptación de nuestra política de cookies, pinche el enlace para mayor información.plugin cookies

ACEPTAR
Aviso de cookies
A %d blogueros les gusta esto: