773 lines
30 KiB
Plaintext
773 lines
30 KiB
Plaintext
# tutorial de uxn: día 3, saltos condicionales y el teclado/controlador
|
|
|
|
¡esta es la tercera sección del {tutorial de uxn}!
|
|
|
|
aquí introducimos el uso del dispositivo controlador en la computadora uxn varvara: esto nos permitirá añadir interactividad a nuestros programas, y empezar a discutir el flujo de control en uxntal.
|
|
|
|
también hablamos de las instrucciones lógicas y de manipulación de la pila en uxntal.
|
|
|
|
# el dispositivo controlador
|
|
|
|
el dispositivo controlador del ordenador varvara nos permite leer las entradas del teclado y/o de los botones del controlador.
|
|
|
|
la definición de sus puertos tendría el siguiente aspecto en un programa típico:
|
|
|
|
```
|
|
|80 @Controlador [ &vector $2 &boton $1 &tecla $1 ]
|
|
```
|
|
|
|
## el byte de botón
|
|
|
|
el byte de botón codifica en cada uno de sus ocho bits el estado de ocho "botones" diferentes, basados en la disposición del controlador de NES.
|
|
|
|
=> https://wiki.nesdev.com/w/index.php/Standard_controller controlador NES estándar
|
|
|
|
numerando los bits de derecha a izquierda, y de 0 a 7, las teclas correspondientes (y los botones de NES) son
|
|
|
|
+ <table>
|
|
+ <tr><th>bit 7</th><th>bit 6</th><th>bit 5</th><th>bit 4</th><th>bit 3</th><th>bit 2</th><th>bit 1</th><th>bit 0</th></tr>
|
|
+ <tr><td>Derecha</td><td>Izquierda</td><td>Abajo</td><td>Arriba</td><td>Esc ("Start" o iniciar))</td><td>Shift ("Select" o seleccionar)</td><td>Alt (botón B)</td><td>Ctrl (botón A)</td></tr>
|
|
+ </table>
|
|
& * 0: Ctrl (botón A)
|
|
& * 1: Alt (botón B)
|
|
& * 2: Shift (botón "Select" o seleccionar)
|
|
& * 3: Esc (botón "Start" o iniciar)
|
|
& * 4: Arriba
|
|
& * 5: Abajo
|
|
& * 6: Izquierda
|
|
& * 7: Derecha
|
|
|
|
codificar los estados de los botones de esta manera nos permite presionar y leer muchas de estas teclas al mismo tiempo.
|
|
|
|
## el byte de la tecla
|
|
|
|
el byte de la tecla almacena el código ascii de la tecla del teclado que se está pulsando en ese momento.
|
|
|
|
la diferencia entre el byte 'de la tecla' y el byte 'de botón' puede ser confusa, especialmente cuando se ejecuta varvara desde uxnemu donde los botones están en el mismo lugar que las teclas.
|
|
|
|
una posible manera de recordar podría ser pensar en el byte 'botón' como refiriéndose a un controlador de gamepad.
|
|
|
|
## el vector del controlador
|
|
|
|
en el contexto de la programación de uxn, un vector se refiere a una dirección en la memoria principal donde se asigna a uxn para que salte cuando ocurra un evento específico.
|
|
|
|
en el caso del vector controlador, este evento específico consiste en cada vez que se pulsa o suelta una tecla.
|
|
|
|
en otras palabras: uxn saltará a la dirección asignada como vector controlador, cada vez que se pulse o suelte una tecla.
|
|
|
|
la siguiente línea de código asignaría ese vector, utilizando la dirección absoluta de la etiqueta en-controlador:
|
|
|
|
```
|
|
;en-controlador .Controlador/vector DEO2
|
|
```
|
|
|
|
¡veamos a continuación como funcionaría!
|
|
|
|
# flujo de control: subrutinas vectoriales
|
|
|
|
hasta ahora nuestros programas uxntal han seguido un flujo lineal: comienzan en la dirección 0100, y terminan en la primera instrucción BRK que se encuentra.
|
|
|
|
podemos pensar en estos programas como rutinas de configuración: configuran los colores del sistema, pueden dibujar o imprimir algunas cosas, y luego dejan a uxn esperando. ¿qué estaría esperando uxn?
|
|
|
|
sí, una opción sería: ¡esperar la entrada del teclado!
|
|
|
|
vamos a empezar a organizar nuestros programas uxntal en términos de subrutinas que corresponden a diferentes vectores.
|
|
|
|
cada una de estas subrutinas terminará con la instrucción BRK, para que puedan hacer que uxn vuelva al estado de espera.
|
|
|
|
## subrutina del vector controlador
|
|
|
|
para ilustrar ese comportamiento, leamos el siguiente programa.
|
|
|
|
este utiliza el procedimiento de dibujo de sprites que probamos el día anterior, pero hace que ocurra sólo cuando se pulsa una tecla. al principio, la pantalla está vacía, y cuando pulsamos una tecla se dibuja un cuadrado:
|
|
|
|
```
|
|
( hola-teclado.tal )
|
|
|
|
( dispositivos )
|
|
|00 @Sistema [ &vector $2 &pad $6 &r $2 &g $2 &b $2 ]
|
|
|20 @Pantalla [ &vector $2 &ancho $2 &alto $2 &pad $2 &x $2 &y $2 &direc $2 &pixel $1 &sprite $1 ]
|
|
|80 @Controlador [ &vector $2 &boton $1 &tecla $1 ]
|
|
|
|
( programa principal )
|
|
|0100
|
|
( establecer los colores del sistema )
|
|
#2ce9 .Sistema/r DEO2
|
|
#01c0 .Sistema/g DEO2
|
|
#2ce5 .Sistema/b DEO2
|
|
|
|
( asignar vector del controlador )
|
|
;en-controlador .Controlador/vector DEO2
|
|
BRK
|
|
|
|
( ejecutar este código cada vez que se pulse o suelte una tecla )
|
|
@en-controlador ( -> )
|
|
( establecer coordenadas x,y )
|
|
#0008 .Pantalla/x DEO2
|
|
#0008 .Pantalla/y DEO2
|
|
|
|
( establecer la direccion del sprite )
|
|
;cuadrado .Pantalla/direc DEO2
|
|
|
|
( dibujar el sprite en el fondo )
|
|
( usando el color 1 para el contorno )
|
|
#01 .Pantalla/sprite DEO
|
|
BRK
|
|
|
|
( sprite )
|
|
@cuadrado ff81 8181 8181 81ff
|
|
```
|
|
|
|
bonito, ¿no?
|
|
|
|
ahora, ¿cómo podemos realizar diferentes acciones dependiendo de la tecla que se haya pulsado?
|
|
|
|
en primer lugar, tenemos que hacer que nuestro programa sepa qué tecla se ha pulsado para que pueda actuar en consecuencia.
|
|
|
|
para ello, veamos algunas instrucciones uxntal nuevas.
|
|
|
|
# instrucciones de comparación y lógica
|
|
|
|
## instrucciones de comparación
|
|
|
|
uxntal tiene cuatro instrucciones para comparar los dos primeros elementos de la pila:
|
|
|
|
* EQU: empuja 01 hacia abajo en la pila si los dos elementos superiores de la pila son iguales, o empuja 00 en caso contrario ( a b -- a==b )
|
|
* NEQ: empuja 01 hacia abajo en la pila si los dos elementos superiores de la pila no son iguales, o empuja 00 en caso contrario ( a b -- a!=b )
|
|
* GTH: empuja 01 hacia abajo en la pila si el primer elemento es mayor que el segundo, o empuja 00 en caso contrario ( a b -- a>b )
|
|
* LTH: empuja 01 hacia abajo en la pila si el primer elemento es menor que el segundo, o empuja 00 en caso contrario ( a b -- a>b )
|
|
|
|
podemos pensar en los resultados empujados por estas instrucciones como banderas booleanas: son 01 si la comparación fue verdadera, y son 00 si fue falsa.
|
|
|
|
el siguiente código leerá el valor de la tecla del controlador, y empujará a la pila una bandera correspondiente a que sea igual al carácter 'a':
|
|
|
|
```
|
|
.Controlador/tecla DEI ( lee la tecla y la empuja hacia abajo en la pila )
|
|
LIT 'a ( empuja el código ascii del carácter 'a' )
|
|
EQU ( compara ambos bytes y empuja 01 si son iguales, 00 si no )
|
|
```
|
|
|
|
EQU2, NEQ2, GTH2 y LTH2 funcionarán de la misma manera, pero comparando cortos en lugar de bytes.
|
|
|
|
## instrucciones lógicas
|
|
|
|
uxntal tiene tres instrucciones lógicas a nivel de bit.
|
|
|
|
pueden funcionar como operadores lógicos que utilizan como operandos las banderas dadas por las instrucciones de comparación que hemos comentado anteriormente:
|
|
|
|
* AND: realiza un AND a nivel de bits con los dos elementos superiores de la pila, y empuja el resultado ( a b -- a&b )
|
|
* ORA: realiza un OR a nivel de bits con los dos primeros elementos de la pila, y empuja el resultado ( a b -- a|b )
|
|
* EOR: realiza un OR exclusivo a nivel de bits con los dos primeros elementos de la pila, y empuja hacia abajo el resultado ( a b -- a^b )
|
|
|
|
AND2, ORA2, EOR2 funcionarán de la misma manera, pero con cortos en lugar de bytes.
|
|
|
|
### AND
|
|
|
|
lo siguiente empujará hacia abajo en la pila una bandera que indica si el byte tecla está entre 30 y 39 inclusive, usando 01 para representar 'verdadero', y 00 para representar 'falso':
|
|
|
|
```
|
|
.Controlador/tecla DEI ( lee la tecla y la introduce en la pila )
|
|
#2f GTH ( ¿es mayor que 2f? empuja la bandera a la pila )
|
|
Controlador/tecla DEI ( lee la tecla y la introduce en la pila )
|
|
#3a LTH ( ¿es menor que 3a? empuja la bandera a la pila )
|
|
AND ( aplica un AND a las banderas de la pila, y empuja el resultado a la pila )
|
|
```
|
|
|
|
que la instrucción sea a nivel de bit (o "bitwise") significa que aplica la operación AND a cada uno de los bits de los operandos.
|
|
|
|
si ambas banderas fueran "verdaderas":
|
|
|
|
```
|
|
0000 0001 ( verdadero )
|
|
AND 0000 0001 ( verdadero )
|
|
----------
|
|
0000 0001 ( verdadero )
|
|
```
|
|
|
|
si alguno (o ambos) de los indicadores fuera "falso":
|
|
|
|
```
|
|
0000 0001 ( verdadero )
|
|
AND 0000 0000 ( falso )
|
|
----------
|
|
0000 0000 ( falso )
|
|
```
|
|
|
|
como estos indicadores sólo utilizan el bit menos significativo (el bit más a la derecha) para codificar su valor, un AND a nivel de bits es equivalente a un AND lógico convencional.
|
|
|
|
### OR
|
|
|
|
el siguiente código empujará una bandera hacia abajo en la pila si el byte tecla es '1' o 'a':
|
|
|
|
```
|
|
.Controlador/tecla DEI ( lee la tecla y la empuja a la pila )
|
|
LIT '1 EQU ( ¿es '1'? empuja la bandera a la pila )
|
|
.Controlador/tecla DEI ( lee la tecla y empujar a la pila )
|
|
LIT 'a EQU ( ¿es 'a'? empuja la bandera a la pila )
|
|
ORA ( aplica un OR a las banderas en la pila, y empuja el resultado en la pila )
|
|
```
|
|
|
|
cuando alguna o ambas banderas son verdaderas, la bandera será verdadera:
|
|
|
|
```
|
|
0000 0001 ( verdadero )
|
|
OR 0000 0000 ( falso )
|
|
----------
|
|
0000 0001 ( verdadero )
|
|
```
|
|
|
|
sólo cuando ambas banderas sean falsas, la bandera resultante será falsa.
|
|
|
|
### EOR
|
|
|
|
un "exclusive-OR", o también "o-exclusivo", es una operación lógica que tiene un resultado de verdadero sólo cuando una u otra entrada es verdadera. si ambas entradas son verdaderas, o si ambas entradas son falsas, el resultado es falso.
|
|
|
|
basándose en este comportamiento, esta instrucción puede utilizarse para invertir el valor de una bandera utilizando un valor especial en el que el/los bit(s) que queremos invertir se pongan a 1. este tipo de valores se llaman máscaras.
|
|
|
|
por ejemplo, el siguiente código empujará una bandera correspondiente a que la te sea mayor o igual a 20, calculando primero si es menor que 20, y luego invirtiendo el resultado:
|
|
|
|
```
|
|
.Controlador/tecla DEI ( lee la tecla y la empuja a la pila )
|
|
#20 LTH ( ¿es menor que 20? empuja la bandera a la pila )
|
|
#01 EOR ( invierte el bit más a la derecha de la bandera y empuja el resultado a la pila )
|
|
```
|
|
|
|
cuando la bandera original es verdadera, lo que significa que el valor de la tecla es menor que 20, el EOR la invertirá y la hará falsa: el valor NO es mayor o igual que 20:
|
|
|
|
```
|
|
0000 0001 ( verdadero )
|
|
EOR 0000 0001 ( máscara )
|
|
----------
|
|
0000 0000 ( falso )
|
|
```
|
|
|
|
como podemos ver, debido a que los dos bits de entrada son 1, el bit de salida es 0.
|
|
|
|
cuando la bandera original es falsa, lo que significa que el valor NO es menor que 20, el EOR lo invertirá y lo hará verdadero: el valor es mayor o igual que 20:
|
|
|
|
```
|
|
0000 0000 ( falso )
|
|
EOR 0000 0001 ( máscara )
|
|
----------
|
|
0000 0001 ( verdadero )
|
|
```
|
|
|
|
observe que la máscara es la misma, y el resultado es el valor opuesto de la bandera.
|
|
|
|
# flujo de control: saltos condicionales
|
|
|
|
ok, ahora nuestros programas pueden identificar y almacenar en banderas si un valor (como la tecla de teclado leída) es un valor específico, o dentro de algún rango.
|
|
|
|
¿cómo podemos usar estas banderas para tener comportamientos condicionales en nuestros programas, donde se toman diferentes acciones dependiendo de los resultados?
|
|
|
|
¡introduzcamos otro conjunto de nuevas instrucciones para que uxn rompa su flujo lineal!
|
|
|
|
## instrucciones para saltos
|
|
|
|
* JMP: "jump" o salto incondicional a la dirección de la pila ( direc -- )
|
|
* JCN: "conditional jump" o salto condicional: toma una dirección y un valor de la pila, y si el valor no es 00, salta a la dirección; en caso contrario continúa con la siguiente instrucción ( valor direc -- )
|
|
|
|
en modo byte, las direcciones que utilizan estas instrucciones son de un byte.
|
|
|
|
estas direcciones de un byte son relativas y pueden considerarse positivas y negativas: indican cuántos bytes hay que saltar en la memoria principal desde la posición actual del contador de programa, ya sea hacia delante (positivo) o hacia atrás (negativo). el rango de estas direcciones relativas es de -128 a 127 inclusive.
|
|
|
|
en modo corto, las direcciones que toman estas instrucciones son absolutas (es decir, de dos bytes de longitud), pero el valor que toma JCN para decidir sigue siendo un byte.
|
|
|
|
## runas para direcciones
|
|
|
|
hay varias runas que se refieren a direcciones y etiquetas. uxnasm las lee y las convierte a los valores binarios correspondientes.
|
|
|
|
en los días anteriores ya hablamos de algunas de ellas; esta es una recapitulación de las mismas, y una introducción de las nuevas:
|
|
|
|
* dirección literal en la página cero: .etiqueta (un byte)
|
|
* dirección literal en memoria principal: ;label (un corto)
|
|
* dirección literal relativa en la memoria principal: ,label (un byte)
|
|
* dirección cruda ("raw") en la memoria principal: :label (un byte)
|
|
|
|
para definir las etiquetas, utilizamos:
|
|
|
|
* definición de etiquetas: @etiqueta
|
|
* definición de la sub-etiqueta: &subetiqueta, donde esta sub-etiqueta será "hije" de la etiqueta previamente definida
|
|
|
|
y finalmente, para referirse a las etiquetas dentro de nuestro código uxntal, tenemos los siguientes casos:
|
|
|
|
* para una etiqueta principal: utilizar el nombre de la etiqueta
|
|
* para una subetiqueta: utilizar etiqueta/subetiqueta
|
|
* para una subetiqueta local: utilizar &subetiqueta
|
|
|
|
## salto condicional
|
|
|
|
¡unamos todo esto!
|
|
|
|
la siguiente subrutina en-controlador ilustra el uso de los saltos, dibujando nuestro sprite sólo cuando la tecla que se pulsó fue '1':
|
|
|
|
```
|
|
@en-controlador
|
|
.Controlador/tecla DEI ( lee la tecla )
|
|
LIT '1 EQU ( ¿es '1'? )
|
|
|
|
( salta a dibuja-sprite si es el caso )
|
|
,&dibuja-sprite JCN
|
|
|
|
,&fin JMP ( si no, salta al final )
|
|
|
|
&dibuja-sprite
|
|
( fijar coordenadas x,y )
|
|
#0008 .Pantalla/x DEO2
|
|
#0008 .Pantalla/y DEO2
|
|
|
|
( establece la dirección del sprite )
|
|
;cuadrado .Pantalla/direc DEO2
|
|
|
|
( dibuja el sprite en el fondo )
|
|
( usando el color 1 para el contorno )
|
|
#01 .Pantalla/sprite DEO
|
|
|
|
&fin
|
|
BRK
|
|
```
|
|
|
|
nótese el uso de sub-etiquetas "dentro" (después) de en-controlador.
|
|
|
|
también note como la expresión ,&subetiqueta corresponde a la dirección relativa (,) que se necesita para saltar a esa ubicación en el código nombrado con una sub-etiqueta local (&).
|
|
|
|
estas direcciones relativas, de un byte, son utilizadas por JCN o JMP.
|
|
|
|
## saltos condicionales
|
|
|
|
el siguiente código ilustra el uso de muchas condiciones: el color del sprite cambia según se pulsen las teclas 1, 2 o 3.
|
|
|
|
```
|
|
@en-controlador
|
|
( establecer coordenadas x,y )
|
|
#0008 .Pantalla/x DEO2
|
|
#0008 .Pantalla/y DEO2
|
|
|
|
( establecer direccion del sprite )
|
|
;cuadrado .Pantalla/direc DEO2
|
|
|
|
.Controlador/teclaDEI LIT '1 EQU ( ¿es la tecla '1'? )
|
|
,&color-1 JCN ( salta al color-1 si es el caso )
|
|
|
|
.Controlador/tecla DEI LIT '2 EQU ( ¿es la tecla '2'? )
|
|
,&color-2 JCN ( salta al color-2 si es el caso )
|
|
|
|
Controlador/tecla DEI LIT '3 EQU ( ¿es la tecla '3'? )
|
|
,&color-3 JCN ( salta al color-3 si es el caso )
|
|
|
|
( en cualquier otro caso, terminar )
|
|
BRK
|
|
|
|
&color-1
|
|
( dibujar el sprite en el fondo )
|
|
( usando el color 1 para el contorno )
|
|
#01 .Pantalla/sprite DEO
|
|
BRK
|
|
|
|
&color-2
|
|
( dibujar sprite en el fondo )
|
|
( usando el color 2 para el contorno )
|
|
#02 .Pantalla/sprite DEO
|
|
BRK
|
|
|
|
&color-3
|
|
( dibujar sprite en el fondo )
|
|
( usando el color 3 para el contorno )
|
|
#03 .Pantalla/sprite DEO
|
|
BRK
|
|
BRK
|
|
```
|
|
|
|
observe cómo las condiciones se escriben una tras otra: siempre que una bandera es falsa, JCN permite a uxn continuar con la siguiente instrucción en memoria.
|
|
|
|
también note que este código no está optimizado para el tamaño o la velocidad, sino para la legibilidad.
|
|
|
|
estaría en tí, por ejemplo, realizar una aritmética con el valor de la tecla que se pulsó para calcular el color a asignar al sprite - ¡podrías inspirarte en tu macro IMPRIMIR-DIGITO del día 1!
|
|
|
|
# manipulación de la pila
|
|
|
|
hasta ahora hemos estado usando la pila como un lugar para almacenar operandos de instrucciones y sus resultados, ¡pero aún no hemos usado todo el potencial de este entorno basado en la pila!
|
|
|
|
## instrucciones de pila
|
|
|
|
uxntal tiene seis instrucciones que actúan sobre los elementos de la pila más cercanos a la parte superior:g
|
|
|
|
* POP: eliminar el elemento superior de la pila ( a -- )
|
|
* DUP: duplicar; empujar una copia del elemento superior ( a -- a a )
|
|
* SWP: "swap" o intercambiar; cambiar el orden de los dos primeros elementos de la pila ( a b -- b a )
|
|
* NIP: elimina el segundo elemento superior de la pila ( a b -- b )
|
|
* OVR: "over" o encima; empuja una copia del segundo elemento superior ( a b -- a b a )
|
|
* ROT: rotar; reordenar los tres primeros elementos de la pila de forma que el tercero esté ahora en la parte superior ( a b c -- b c a )
|
|
|
|
en modo corto, POP2, DUP2, SWP2, NIP2, OVR2 y ROT2 realizan las mismas acciones pero utilizando cortos en lugar de bytes.
|
|
|
|
## ejemplos
|
|
|
|
vamos a utilizar estas instrucciones de muchas maneras diferentes durante los próximos días.
|
|
|
|
los siguientes son algunos ejemplos basados en fragmentos de código que ya hemos discutido.
|
|
|
|
ten en cuenta que el uso de estas instrucciones puede contribuir a que el código sea difícil de seguir o leer, por lo que siempre será una buena idea utilizarlas dentro de macros o tener comentarios en el código explicando lo que está sucediendo :)
|
|
|
|
### dígito ascii: duplicar e intercambiar
|
|
|
|
discutimos anteriormente este segmento de código, que empuja una bandera que responde si la tecla que se pulsa tiene un código ascii entre 30 y 39, ambos inclusive (es decir, calcula si un byte tiene un código ascii correspondiente a un dígito decimal)
|
|
|
|
```
|
|
.Controlador/tecla DEI ( lee la tecla y la empuja a la pila)
|
|
#2f GTH ( ¿es mayor que 2f? empuja la bandera a la pila )
|
|
Controlador/tecla DEI ( lee la tecla y la introduce en la pila )
|
|
#3a LTH ( ¿es menor que 3a? empuja la bandera a la pila )
|
|
AND ( aplica un AND a las banderas en la pila, y empuja el resultado en la pila )
|
|
```
|
|
|
|
en lugar de leer la tecla dos veces, podríamos hacerlo una vez, y luego usar la instrucción DUP para copiar el valor:
|
|
|
|
```
|
|
.Controlador/tecla DEI DUP ( leer y duplicar la tecla )
|
|
```
|
|
|
|
la pila después de estas instrucciones tendría dos copias del valor de la tecla:
|
|
|
|
```
|
|
tecla tecla <- arriba
|
|
```
|
|
|
|
entonces en nuestro código podemos seguir añadiendo la primera comparación:
|
|
|
|
```
|
|
#2f GTH ( ¿es mayor que 2f? empuja la bandera en la pila )
|
|
```
|
|
|
|
después de esto, la pila se vería como:
|
|
|
|
```
|
|
tecla bandera1 <- arriba
|
|
```
|
|
|
|
para realizar la segunda comparación, necesitamos tener la tecla en la parte superior, no la bandera.
|
|
|
|
¿cómo lo conseguimos? así es, utilizando un SWP:
|
|
|
|
```
|
|
SWP ( poner la tecla en la parte superior )
|
|
```
|
|
|
|
ahora la pila se ve así:
|
|
|
|
```
|
|
bandera1 tecla <- arriba
|
|
```
|
|
|
|
finalmente podemos proceder a la comparación y al AND:
|
|
|
|
```
|
|
#3a LTH ( ¿es menor que 3a? empuja la bandera en la pila )
|
|
AND ( aplica un AND a las banderas en la pila, y empuja el resultado en la pila )
|
|
```
|
|
|
|
terminando con una pila que sólo tiene el resultado:
|
|
|
|
```
|
|
resultado <- arriba
|
|
```
|
|
|
|
el código completo se leería como:
|
|
|
|
```
|
|
.Controlador/tecla DEI DUP ( lee y duplica la tecla )
|
|
#2f GTH ( ¿es mayor que 2f? empuja la bandera a la pila )
|
|
SWP ( poner la tecla en la parte superior )
|
|
#3a LTH ( ¿es menor que 3a? empuja la bandera a la pila )
|
|
AND ( aplica un AND a las banderas en la pila, y empuja el resultado en la pila )
|
|
```
|
|
|
|
el primer código se ensambla en 13 bytes, y éste se ensambla en 12 bytes. quizá no haya demasiada diferencia en ese aspecto.
|
|
|
|
sin embargo, una ventaja más significativa es que esta nueva rutina ahora necesita su entrada empujada hacia abajo en la pila sólo al principio.
|
|
|
|
en el caso que acabamos de discutir la entrada es la tecla que se presiona, pero podríamos fácilmente tener como entrada cualquier otro valor de la pila.
|
|
|
|
esto implica que podríamos escribir la rutina como una macro:
|
|
|
|
```
|
|
%?DIGITO-ASCII { DUP #2f GTH SWP #3a LTH AND } ( byte -- bandera )
|
|
```
|
|
|
|
y utilizarla con el byte que queramos:
|
|
|
|
```
|
|
#30 ?DIGITO-ASCII ( empuja 01 hacia abajo en la pila )
|
|
#20 ?DIGITO-ASCII ( empuja 00 hacia abajo en la pila )
|
|
.Controlador/tecla DEI ?DIGITO-ASCII ( empuja la bandera correspondiente a la pila )
|
|
```
|
|
|
|
### duplicados para los condicionales
|
|
|
|
otro caso anterior en el que repetimos muchas lecturas de la tecla del teclado fue cuando usamos las condicionales múltiples.
|
|
|
|
podríamos reescribirlo usando varios DUPs y POPs:
|
|
|
|
```
|
|
@en-controlador
|
|
( establecer coordenadas x,y )
|
|
#0008 .Pantalla/x DEO2
|
|
#0008 .Pantalla/y DEO2
|
|
|
|
( establecer dirección del sprite )
|
|
;cuadrado .Pantalla/direc DEO2
|
|
|
|
.Controlador/tecla DEI ( leer tecla )
|
|
DUP LIT '1 EQU ( ¿es la tecla '1'? )
|
|
,&color-1 JCN ( salta al color-1 si es el caso )
|
|
|
|
DUP LIT '2 EQU ( ¿es la tecla '2'? )
|
|
,&color-2 JCN ( salta al color-2 si es el caso )
|
|
|
|
DUP LIT '3 EQU ( ¿es la tecla '3'? )
|
|
,&color-3 JCN ( salta al color-3 si es el caso )
|
|
|
|
( en cualquier otro caso, termina )
|
|
POP
|
|
BRK
|
|
|
|
&color-1
|
|
( dibujar el sprite en el fondo )
|
|
( usando el color 1 para el contorno )
|
|
#01 .Pantalla/sprite DEO
|
|
POP
|
|
BRK
|
|
|
|
&color-2
|
|
( dibujar sprite en el fondo )
|
|
( usando el color 2 para el contorno )
|
|
#02 .Pantalla/sprite DEO
|
|
POP
|
|
BRK
|
|
|
|
&color-3
|
|
( dibujar sprite en el fondo )
|
|
( usando el color 3 para el contorno )
|
|
#03 .Pantalla/sprite DEO
|
|
POP
|
|
BRK
|
|
BRK
|
|
```
|
|
|
|
¿puedes decir por qué necesitamos todos esos POPs?
|
|
|
|
pista: compara el estado final de la pila con y sin las instrucciones POP.
|
|
|
|
¡en los próximos días veremos más usos y ejemplos de manipulación de la pila!
|
|
|
|
# botón del controlador
|
|
|
|
la última cosa que discutiremos hoy es el uso del byte del botón del controlador en la computadora varvara.
|
|
|
|
como ya hemos mencionado, la principal diferencia aquí es que este byte mantiene el estado de 8 botones en cada uno de sus bits.
|
|
|
|
dependiendo de nuestra aplicación, podríamos necesitar permitir que algunos de estos botones sean presionados al mismo tiempo.
|
|
|
|
en ese caso, ¿cómo podríamos aislar cada uno de los bits para comprobar su estado individualmente?
|
|
|
|
conoce las máscaras AND a nivel de bits.
|
|
|
|
## máscara AND
|
|
|
|
una máscara AND es un valor especial que utilizaremos para mantener o perder bits específicos de otro valor dado, como el byte del botón del controlador.
|
|
|
|
en nuestra máscara AND, estableceremos como 1 los bits en las posiciones en las que queramos mantener el valor de los bits de entrada. las posiciones en las que los bits de la máscara sean 0 se convertirán en 0 en la entrada.
|
|
|
|
por ejemplo, digamos que queremos ver si el bit número 4, que corresponde al botón Arriba, está encendido o apagado, independientemente del estado de los otros botones.
|
|
|
|
nuestra máscara AND tendrá un 1 en el bit número 4 (de derecha a izquierda, y empezando por el 0), y 0 en el resto:
|
|
|
|
```
|
|
0001 000: 10
|
|
```
|
|
|
|
¿qué pasaría si se pulsa el botón A (tecla Ctrl), con su estado codificado en el bit 0, y nada más?
|
|
|
|
```
|
|
0000 0001 ( botón )
|
|
AND 0001 0000 ( máscara )
|
|
----------
|
|
0000 0000 ( resultado )
|
|
```
|
|
|
|
¿qué ocurre si se pulsa el botón Arriba?
|
|
|
|
```
|
|
0001 0000 ( botón )
|
|
AND 0001 0000 ( máscara )
|
|
----------
|
|
0001 0000 ( resultado )
|
|
```
|
|
|
|
¿y si se pulsan tanto Arriba como Ctrl?
|
|
|
|
```
|
|
0001 0001 ( botón )
|
|
AND 0001 0000 ( máscara )
|
|
----------
|
|
0001 0000 ( resultado )
|
|
```
|
|
|
|
así vemos cómo la máscara nos permite aislar efectivamente el bit que nos importa, independientemente del estado de los otros bits.
|
|
|
|
aplicar esta máscara sería tan sencillo como escribir:
|
|
|
|
```
|
|
#10 AND ( aplicar máscara 0001 000 )
|
|
```
|
|
|
|
## ejemplo: dibujar con flechas y Ctrl
|
|
|
|
=> ./img/screenshot_uxn-draw-with-keyboard.png captura de pantalla de un posible resultado de la ejecución del siguiente programa; muestra un rastro dibujado con cuadrados rellenos o delineados.
|
|
|
|
el siguiente programa uxntal permite dibujar utilizando las teclas de las flechas y la tecla Ctrl (botón A).
|
|
|
|
las flechas mueven la posición de un sprite, y al pulsar Ctrl mientras se mueve lo dibujará con los colores inversos en relleno y trazo.
|
|
|
|
observa el uso de las máscaras AND, los saltos condicionales y algunas operaciones de la pila.
|
|
|
|
```
|
|
( dibujar-con-teclado.tal )
|
|
( dispositivos )
|
|
|00 @Sistema [ &vector $2 &pad $6 &r $2 &g $2 &b $2 ]
|
|
|20 @Pantalla [ &vector $2 &ancho $2 &alto $2 &pad $2 &x $2 &y $2 &direc $2 &pixel $1 &sprite ]
|
|
|80 @Controlador [ &vector $2 &boton $1 &tecla $1 ]
|
|
|
|
( programa principal )
|
|
|0100
|
|
( establecer los colores del sistema )
|
|
#2ce9 .Sistema/r DEO2
|
|
#01c0 .Sistema/g DEO2
|
|
#2ce5 .Sistema/b DEO2
|
|
|
|
( asignar vector del controlador )
|
|
;en-controlador .Controlador/vector DEO2
|
|
|
|
( establecer coordenadas iniciales x,y )
|
|
#0008 .Pantalla/x DEO2
|
|
#0008 .Pantalla/y DEO2
|
|
( establece la dirección del sprite )
|
|
;cuadrado .Pantalla/direc DEO2
|
|
BRK
|
|
|
|
@en-controlador ( -> )
|
|
.Controlador/boton DEI DUP ( leer y duplicar el byte del boton )
|
|
#01 AND ( aislar el bit 0, correspondiente a Ctrl )
|
|
,&relleno JCN ( si el bit no es 0, saltar al relleno, si no, continuar )
|
|
|
|
&contorno
|
|
#01 .Pantalla/sprite DEO ( dibujar contorno )
|
|
,&comprobar-flechas JMP ( continuar con la comprobación de las flechas )
|
|
|
|
&relleno
|
|
#04 .Pantalla/sprite DEO ( dibujar relleno )
|
|
|
|
&comprobar-flechas
|
|
( usar el byte del botón de la pila )
|
|
DUP #10 AND ( aislar el bit 4, correspondiente a Arriba )
|
|
,&arriba JCN ( saltar si no es 0 )
|
|
DUP #20 AND ( aísla el bit 5, correspondiente a Abajo )
|
|
&down JCN ( salta si no es 0 )
|
|
DUP #40 AND ( aislar el bit 6, correspondiente a Izquierda )
|
|
,&left JCN ( salta si no es 0 )
|
|
DUP #80 AND ( aísla el bit 7, correspondiente a Derecha )
|
|
&right JCN ( salta si no es 0 )
|
|
|
|
POP BRK
|
|
|
|
&arriba
|
|
.Pantalla/y DEI2 #0008 SUB2 .Pantalla/y DEO2 ( disminuye y )
|
|
POP
|
|
BRK
|
|
&abajo
|
|
.Pantalla/y DEI2 #0008 ADD2 .Pantalla/y DEO2 ( incrementa y )
|
|
POP
|
|
BRK
|
|
&izquierda
|
|
.Pantalla/x DEI2 #0008 SUB2 .Pantalla/x DEO2 ( disminuye x )
|
|
POP
|
|
BRK
|
|
&derecha
|
|
.Pantalla/x DEI2 #0008 ADD2 .Pantalla/x DEO2 ( incrementa x )
|
|
POP
|
|
BRK
|
|
BRK
|
|
( sprite )
|
|
@cuadrado ff81 8181 8181 81ff
|
|
```
|
|
|
|
algunas posibilidades para que practiques:
|
|
|
|
* modificar el código para que también responda a que pulses más de una flecha al mismo tiempo.
|
|
* convertir los incrementos y decrementos de las coordenadas en macros que tomen la dirección del puerto como entrada, y realizar una operación equivalente. estas dos líneas deberían funcionar usando la misma macro:
|
|
|
|
```
|
|
.Pantalla/x INCREMENTO
|
|
.Pantalla/y INCREMENTO
|
|
```
|
|
|
|
recuerde que .Pantalla/x es una dirección literal en la página cero, es decir, empuja un byte correspondiente a la dirección de la sub-etiqueta Pantalla/x :)
|
|
|
|
# practica posibilidades
|
|
|
|
¡aquí tienes otras ideas para que practiques con lo que hemos cubierto hoy!
|
|
|
|
* dibujar un controlador virtual que muestre cuáles de sus botones, mapeados a teclas del teclado, están siendo presionados
|
|
* crear una especie de máquina de escribir que dibuje diferentes símbolos y mueva el cursor de dibujo dependiendo de la tecla que se haya pulsado
|
|
* dibujar un personaje que cambie su estado según la tecla que se haya pulsado. ¿tal vez utilizar múltiples tiles para dibujarlo?
|
|
* crea un simple tablero de tres-en-raya para dos jugadores: una tecla dibuja una X, otra dibuja una O, y las flechas permiten elegir la casilla a dibujar.
|
|
|
|
¡ten en cuenta que para un suave movimiento interactivo puede ser mejor utilizar el vector de pantalla que se llama 60 veces por segundo!
|
|
|
|
¡lo cubriremos en profundidad en el próximo día del tutorial!
|
|
|
|
# instrucciones del día 3
|
|
|
|
¡estas son todas las instrucciones uxntal de las que hemos hablado hoy!
|
|
|
|
## instrucciones de comparación
|
|
|
|
* EQU: empuja 01 hacia abajo en la pila si los dos primeros elementos de la pila son iguales, o empuja 00 en caso contrario ( a b -- a==b )
|
|
* NEQ: empuja 01 hacia abajo en la pila si los dos primeros elementos de la pila no son iguales, o empuja 00 en caso contrario ( a b -- a!=b )
|
|
* GTH: empuja 01 hacia abajo en la pila si el primer elemento es mayor que el segundo, o empuja 00 en caso contrario ( a b -- a>b )
|
|
* LTH: empuja 01 hacia abajo en la pila si el primer elemento es menor que el segundo, o empuja 00 en caso contrario ( a b -- a<b )
|
|
|
|
## lógica a nivel de bits
|
|
|
|
* AND: realiza un AND a nivel de bits con los dos primeros elementos de la pila, y empuja hacia abajo el resultado ( a b -- a&b )
|
|
* ORA: realiza un OR a nivel de bits con los dos primeros elementos de la pila, y empuja el resultado ( a b -- a|b )
|
|
* EOR: realiza un OR exclusivo a nivel de bits con los dos primeros elementos de la pila, y empuja hacia abajo el resultado ( a b -- a^b )
|
|
|
|
## saltos
|
|
|
|
* JMP: salto incondicional a la dirección de la pila ( direc -- )
|
|
* JCN: salto condicional: toma una dirección y un valor de la pila, y si el valor no es 00, salta a la dirección; en caso contrario continúa con la siguiente instrucción ( valor direc -- )
|
|
|
|
## pila
|
|
|
|
* POP: eliminar el elemento superior de la pila ( a -- )
|
|
* DUP: duplicar; empujar una copia del elemento superior ( a -- a a )
|
|
* SWP: "swap" o intercambiar; cambiar el orden de los dos primeros elementos de la pila ( a b -- b a )
|
|
* NIP: elimina el segundo elemento superior de la pila ( a b -- b )
|
|
* OVR: "over" o encima; empuja una copia del segundo elemento superior ( a b -- a b a )
|
|
* ROT: rotar; reordenar los tres primeros elementos de la pila de forma que el tercero esté ahora en la parte superior ( a b c -- b c a )
|
|
|
|
# día 4
|
|
|
|
en el {tutorial de uxn día 4} cubrimos el uso del vector de pantalla para crear animaciones, ya sean interactivas o no.
|
|
|
|
¡también exploramos las posibilidades de usar "variables" en uxntal que pueden ayudarnos a crear programas más elaborados!
|
|
|
|
¡antes de entrar en materia, les invito a seguir explorando y también a tomarse un descanso!
|
|
|
|
¡manténgase en sintonía!
|
|
|
|
# apoyo
|
|
|
|
si te ha gustado este tutorial y te ha resultado útil, considera compartirlo y darle tu {apoyo} :)
|