diff --git a/src/tutorial_de_uxn_día_2.gmo b/src/tutorial_de_uxn_día_2.gmo index 60d0fc0..097c0d6 100644 --- a/src/tutorial_de_uxn_día_2.gmo +++ b/src/tutorial_de_uxn_día_2.gmo @@ -1,3 +1,1085 @@ -# tutorial uxn: día 2 +tutorial de uxn: día 2, la pantalla -traducción en proceso +¡esta es la segunda sección del tutorial de uxn! + +en esta sección vamos a empezar a explorar los aspectos visuales de la computadora varvara ¡hablamos sobre los aspectos fundamentales del dispositivo de pantalla para que podamos empezar a dibujar en ella! + +también discutiremos el trabajo con instrucciones de salto corto ("short"; 2 bytes) junto a los de un sólo byte en uxntal. + +si todavía no lo has hecho te recomiendo que leas las sección anterior: + +=> ./tutorial_de_uxn_día_1.gmi {tutorial de uxn día 1} + +# donde están tus cortos? + +antes de pasar a dibujar en la pantalla, tenemos que hablar de bytes y cortos :) + +## bytes y cortos + +aunque uxn es un ordenador que trabaja de forma nativa con palabras de 8 bits (bytes), nos encontramos en varias ocasiones que la cantidad de datos que es posible almacenar en un byte no es suficiente. + +cuando utilizamos 8 bits, podemos representar 256 valores diferentes (2 a la potencia de 8). en cualquier momento dado, un byte almacenará sólo uno de esos posibles valores. + +en la sección anterior, hablamos de un caso en el que esta cantidad no es suficiente en uxn: el número de bytes que alberga la memoria principal, 65536. + +ese número corresponde a los valores que se pueden representar usando dos bytes, o 16 bits, o un "corto": 2 a la potencia de 16. esa cantidad también se conoce como 64KB, donde 1KB corresponde a 1024 o 2 a la potencia de 10. + +además de expresar direcciones en la memoria principal, hoy veremos otro caso en el que 256 valores no siempre son suficientes: las coordenadas x e y de los píxeles de nuestra pantalla. + +para estos y otros casos, usar shorts en lugar de bytes será el camino a seguir. + +¿cómo los tratamos? + +## el modo corto + +contando de derecha a izquierda, el 6to bit de un byte que codifica una instrucción para el ordenador uxn es una bandera binaria que indica si el modo corto está activado o no. + +siempre que el modo corto esté activado, es decir, cuando ese bit sea 1 en lugar de 0, la cpu uxn realizará la instrucción dada por los 5 primeros bits (el opcode) pero utilizando pares de bytes en lugar de bytes individuales. + +el byte que esté más adentro de la pila será el byte "alto" del corto, y el byte que esté más cerca de la parte superior de la pila será el byte "bajo" del corto. + +en uxntal, indicamos que queremos poner esta bandera añadiendo el dígito '2' al final de una instrucción mnemónica. + +veamos algunos ejemplos! + +## ejemplo del modo corto + +###LIT2 + +en primer lugar, recapitulemos. el siguiente código empujará el número 02 hacia abajo en la pila, luego empujará el número 30 (hexadecimal) hacia abajo en la pila, y finalmente los sumará, dejando el número 32 en la pila: +``` +#02 #30 ADD +``` +este sería el estado final de la pila: +``` +32 <- arriba +``` +en el día anterior mencionamos que la runa hexadecimal literal (#) es una abreviatura de la instrucción LIT. por lo tanto, podríamos haber escrito nuestro código de la siguiente manera: +``` +LIT 02 LIT 30 ADD ( código ensamblado: 80 02 80 30 18 ) +``` + +ahora, si añadimos el sufijo '2' a la instrucción LIT, podríamos escribir en su lugar: + +``` +LIT2 02 30 ADD ( código ensamblado: a0 02 30 18 ) +``` + +podemos utilizar la runa hexadecimal literal (#) con un corto (cuatro nibbles) en lugar de un byte (dos nibbles), y funcionará como una abreviatura de LIT2: + +``` +#0230 ADD +``` + +### ADD2 + +ahora veamos que pasa con la instrucción ADD cuando usamos el modo corto. + +¿cuál sería el estado de la pila después de ejecutar este código? + +``` +#0004 #0008 ADD +``` + +así es! la pila tendrá los siguientes valores, porque estamos empujando 4 bytes hacia abajo en la pila, sumando (ADD) los dos más cercanos a la parte superior, y empujando el resultado hacia abajo en la pila. + +``` +00 04 08 <- arriba +``` + +ahora, comparemos con lo que ocurre con el ADD2: + +``` +#0004 #0008 ADD2 +``` + +en este caso estamos empujando los mismos 4 bytes hacia abajo en la pila, pero ADD2 está haciendo las siguientes acciones: +* toma el elemento superior de la pila (08) y lo almacena como el byte bajo del primer corto +* toma el nuevo elemento superior de la pila (00), y lo almacena como el byte alto del primer corto, que ahora es 0008 +* toma el nuevo elemento superior de la pila (04), y lo almacena como el byte bajo del segundo corto +* toma el nuevo elemento superior de la pila (00), y lo almacena como el byte alto del segundo corto, que ahora es 0004 +* suma los dos cortos (0004 + 0008), obteniendo un resultado de 000c +* empuja el byte alto del resultado (00) hacia abajo en la pila +* empuja el byte bajo del resultado (0c) hacia abajo en la pila + +the stack ends up looking as follows: + +``` +00 0c <- arriba +``` + +puede que no necesitemos pensar demasiado en las manipulaciones por byte de las operaciones aritméticas, porque normalmente podemos pensar que están haciendo la misma operación que antes, pero utilizando pares de bytes en lugar de bytes individuales. su orden no cambia realmente. + +en cualquier caso, es útil tener en cuenta cómo funcionan para algunos comportamientos que podríamos necesitar más adelante :) + +### DEO2, DEI, DEI2 + +hablemos ahora de la instrucción DEO ("device out" o salida de dispositivo) de la que hablamos el día anterior, ya que su modo corto implica algo especial. + +la instrucción DEO necesita un valor (1 byte) para salir, y una dirección entrada/salida (1 byte) en la pila, para poder sacar ese valor en esa dirección. + +``` +DEO ( valor dirección -- ) +``` + +esta instrucción tiene una contrapartida: DEI ("device in" o entrada de dispositivo). + +la instrucción DEI toma una dirección de entrada/salida (1 byte) de la pila, y va a empujar hacia abajo en la pila el valor (1 byte) que corresponde a la lectura de esa entrada. + +``` +DEI ( dirección -- valor ) +``` + +¿qué crees que harán DEO2 y DEI2? + +en el caso del modo corto de DEO y DEI, el aspecto corto se aplica al valor de salida o entrada, y no a la dirección. + +recuerda que las 256 direcciones de entrada/salida ya están cubiertas usando un solo byte, por lo que usar un corto para ellas sería redundante: el byte alto sería siempre 00. + +considerando esto, los siguientes son los comportamientos que podemos esperar: + +la instrucción DEO2 necesita un valor (1 short) para salir, y una dirección i/o (1 byte) en la pila, para poder sacar ese valor a esa dirección. por lo tanto necesita un total de 3 bytes en la pila para operar. + +por otro lado, la instrucción DEI2 necesita una dirección i/o (1 byte) en la pila, y empujará hacia abajo en la pila el valor (1 short) que corresponde a esa entrada. + +en la siguiente sección veremos algunos ejemplos en los que podremos utilizar estas instrucciones. + +el puerto de 'escritura' del dispositivo de la consola que utilizamos la última vez tiene un tamaño de 1 byte, por lo que no podemos utilizar estas nuevas instrucciones de forma significativa con él. + +# dispositivo de sistema y colores + +el dispositivo del sistema es el dispositivo varvara con una dirección de 00. sus puertos de salida (que comienzan en la dirección 08) corresponden a tres cortos diferentes: uno llamado rojo (r), el otro verde (g), y el último azul (b). + +en los ejemplos uxntal podemos ver sus etiquetas definidas de la siguiente manera: + +``` +|00 @Sistema [ &vector $2 &pad $6 &r $2 &g $2 &b $2 ] +``` + +ignoraremos los primeros elementos por el momento, y nos centraremos en los componentes de color. +dispositivo de sistema y colores + +## colores del sistema + +el dispositivo de pantalla varvara sólo puede mostrar un máximo de cuatro colores a la vez. + +estos cuatro colores se denominan color 0, color 1, color 2 y color 3. + +cada color tiene una profundidad total de 12 bits: 4 bits para el componente rojo, 4 bits para el componente verde y 4 bits para el componente azul. + +podemos definir los valores de estos colores fijando los valores r, g, b del dispositivo del sistema. + +podemos escribirlo de la siguiente manera: + +``` +( hola-pantalla.tal ) + +( dispositivos ) +|00 @Sistema [ &vector $2 &pad $6 &r $2 &g $2 &b $2 ] + +( programa principal ) +|0100 + ( establecer colores del sistema ) + #2ce9 .Sistema/r DEO2 + #01c0 .Sistema/g DEO2 + #2ce5 .Sistema/b DEO2 +``` + +¿cómo podríamos leer lo que significan esos cortos literales? + +podemos leer cada uno de los colores verticalmente, de izquierda a derecha: + +* el color 0 sería el rojo: 2, verde: 0, azul: 2 ( #220022 en notación de color hexadecimal, púrpura oscuro ) +* el color 1 sería rojo: c, verde: 1, azul: c ( #cc11cc en notación de color hexadecimal, magenta ) +* el color 2 sería rojo: e, verde: c, azul: e ( #eeccee en notación de color hexadecimal, rosa claro ) +* el color 3 sería rojo 9, verde: 0, azul: 5 ( #990055 en notación de color hexadecimal, rojo oscuro ) + +si ejecutamos el programa ahora veremos una pantalla de color púrpura oscuro, en lugar de negro como lo que teníamos antes. + +prueba a cambiar los valores del color 0, es decir, la primera columna, y mira lo que pasa :) + +# el dispositivo de pantalla + +como recapitulación: mencionamos que el dispositivo de pantalla sólo puede mostrar cuatro colores diferentes en un momento dado, y que estos colores están numerados del 0 al 3. fijamos estos colores usando los puertos correspondientes en el dispositivo del sistema. + +¡ahora hablemos del dispositivo de pantalla y empecemos a usarlo! + +## entradas y salidas + +en los programas uxntal para el ordenador varvara puedes encontrar las etiquetas correspondientes a este dispositivo de la siguiente manera: + +``` +|20 @Pantalla [ &vector $2 &ancho $2 &alto $2 &pad $2 &x $2 &y $2 &direc $2 &pixel $1 &sprite $1 ] +``` + +las entradas que podemos leer de este dispositivo son + +* vector (2 bytes) +* anchura de la pantalla (2 bytes) +* altura de la pantalla (2 bytes) + +y los puertos de salida de este dispositivo son: + +* coordenada x (2 bytes) +* coordenada y (2 bytes) +* dirección de memoria (2 bytes) +* píxel (1 byte) +* sprite (1 byte) + +## primer plano y plano de fondo + +el dispositivo de pantalla tiene dos capas superpuestas del mismo tamaño, el primer plano y el plano de fondo. + +lo que se dibuje sobre la capa del primer plano cubrirá todo lo que se dibuje en la misma posición en la capa del plano de fondo. + +al principio, la capa del primer plano es completamente transparente: un proceso de mezcla alfa asegura que podamos ver la capa de fondo. + +# dibujando un píxel + +la primera y más sencilla forma de dibujar en la pantalla es dibujando un solo píxel. + +para hacer esto necesitamos establecer un par de coordenadas x,y donde queremos que se dibuje el pixel, y necesitamos establecer el byte 'pixel' a un valor específico para realizar realmente el dibujo. + +## estableciendo las coordenadas + +las coordenadas x,y siguen las convenciones comunes a otros programas de gráficos por ordenador: + +* x comienza en 0 a la izquierda, y aumenta hacia la derecha de la pantalla +* y comienza en 0 en la parte superior, y aumenta hacia la parte inferior de la pantalla + +si quisiéramos dibujar un píxel en coordenadas ( 8, 8 ), estableceríamos sus coordenadas de esta manera: + +``` +#0008 .Pantalla/x DEO2 +#0008 .Pantalla/y DEO2 +``` + +alternativamente, podríamos empujar primero los valores de las coordenadas hacia abajo en la pila, y la salida de ellos después: + +``` +#0008 #0008 .Pantalla/x DEO2 .Pantalla/y DEO2 +``` + +una pregunta para ti: si quisiéramos establecer las coordenadas como ( x: 4, y: 8 ), ¿cuál de los cortos en el código anterior deberías cambiar por 0004? + +## estableciendo el color + +el envío de un único byte a .Pantalla/pixel realizará el dibujo en la pantalla. + +el nibble alto de ese byte, es decir, el dígito hexadecimal de la izquierda, determinará la capa en la que dibujaremos: +* 0: dibujar un solo píxel en el fondo +* 4: dibujar un solo píxel en el primer plano +palabras en +el nibble inferior del byte, es decir, el dígito hexadecimal de la derecha, determinará su color. + +las 8 posibles combinaciones del byte 'pixel' que tenemos para dibujar un pixel son: + +* 00: dibujar el píxel con el color 0 en la capa del fondo +* 01: dibujar un píxel con el color 1 en la capa del fondo +* 02: dibujar el píxel con el color 2 en la capa del fondo +* 03: dibujar el píxel con el color 3 en la capa del fondo +* 40: dibujar un píxel con el color 0 en la capa del primer plano +* 41: dibujar el píxel con el color 1 en la capa del primer plano +* 42: dibujar el píxel con el color 2 en la capa del primer plano +* 43: dibujar el píxel con el color 3 en la capa del primer plano + +##hola píxel + +el siguiente código dibujará un píxel con el color 1 en la capa del primer plano, en las coordenadas (8,8): + +``` +#0008 .Pantalla/x DEO2 +#0008 .Pantalla/y DEO2 +#41 .Pantalla/pixel DEO +``` + +el programa completo se vería de la siguiente manera: + +``` +( hola-pixel.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 ] + +( programa principal ) +|0100 + ( establecer colores del sistema ) + #2ce9 .Sistema/r DEO2 + #01c0 .Sistema/g DEO2 + #2ce5 .Sistema/b DEO2 + + ( dibujar un pixel en la pantalla ) + #0008 .Pantalla/x DEO2 + #0008 .Pantalla/y DEO2 + #41 .Pantalla/pixel DEO ( capa de primer plano, color 1 ) +``` + +¡wuju! + +recuerda que puedes usar F1 para cambiar de nivel de zoom, y F3 para hacer capturas de pantalla de tus bocetos :) + +## hola píxeles + +los valores que establecemos en las coordenadas x e y permanecen ahí hasta que los sobrescribimos. + +Por ejemplo, podemos dibujar múltiples píxeles en una línea horizontal, estableciendo la coordenada "y" sólo una vez: + +``` +( establecer coordenadas y ) +#0008 .Pantalla/y DEO2 + +( dibujar 6 píxeles en una línea horizontal ) +#0008 .Pantalla/x DEO2 +#41 .Pantalla/pixel DEO + +#0009 .Pantalla/x DEO2 +#41 .Pantalla/pixel DEO + +#000a .Pantalla/x DEO2 +#41 .Pantalla/pixel DEO + +#000b .Pantalla/x DEO2 +#41 .Pantalla/pixel DEO + +#000c .Pantalla/x DEO2 +#41 .Pantalla/pixel DEO + +#000d .Pantalla/x DEO2 +#11 .Pantalla/pixel DEO +``` + +nótese que tenemos que establecer el color para cada píxel que dibujamos; esa operación señala el dibujo y tiene que repetirse. + +podemos definir una macro para que este proceso sea más fácil de escribir: + +``` +%DIBUJAR-PIXEL { #41 .Pantalla/pixel DEO } ( -- ) +``` + +## leyendo y manipulando coordenadas + +todavía no cubriremos las estructuras repetitivas, pero esta es una buena oportunidad para empezar a alinear nuestro código hacia eso. + +aunque las coordenadas x e y del dispositivo de pantalla están pensadas como salidas, también podemos leerlas como entradas. + +por ejemplo, para leer la coordenada x, empujando su valor hacia abajo en la pila, podemos escribir: + +``` +.Pantalla/x DEI2 +``` + +teniendo en cuenta esto, ¿se puede saber qué haría este código? + +``` +.Pantalla/x DEI2 +#0001 ADD2 +.Pantalla/x DEO2 +``` + +¡lo has adivinado bien, espero! + +* la primera línea empuja la coordenada x como un corto, hacia abajo en la pila. +* la segunda línea empuja el número 0001, lo añade al corto anterior, y empuja el resultado a la pila. +* la tercera línea toma el resultado de la pila y lo escribe como la nueva coordenada x. + +ese conjunto de instrucciones incrementa la coordenada x de la pantalla por uno :) + +parecen útiles, así que también podríamos guardarlos como una macro: + +``` +%INC-X { .Pantalla/x DEI2 #0001 ADD2 .Pantalla/x DEO2 } ( -- ) +``` + +aquí hay otra pregunta para ti: ¿cómo escribirías una macro ADD-X que te permita incrementar la coordenada x en una cantidad arbitraria que pongas en la pila? + +``` +%ADD-X { } ( incremento -- ) +``` + +## instrucción INC + +añadir 1 al valor de la parte superior de la pila es tan común que hay una instrucción para conseguirlo utilizando menos espacio, INC: + +``` +INC ( a -- a+1 ) +``` + +INC toma el valor de la parte superior de la pila, lo incrementa por uno y lo empuja de vuelta. + +en el caso del modo corto, INC2 hace lo mismo pero incrementando un corto en lugar de un byte. + +nuestra macro para incrementar la coordenada x podría entonces escribirse como sigue: + +``` +%INC-X { .Pantalla/x DEI2 INC2 .Pantalla/x DEO2 } ( -- ) +``` + +## hola píxeles usando macros + +usando estas macros que definimos arriba, nuestro código puede terminar viéndose de la siguiente forma: + +``` +( hola-pixeles.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 ] + +( macros ) +%DIBUJAR-PIXEL { #41 .Pantalla/pixel DEO } ( -- ) +%INC-X { .Pantalla/x DEI2 INC2 .Pantalla/x DEO2 } ( -- ) + +( programa princpal ) +|0100 + #2ce9 .Sistema/r DEO2 + #01c0 .Sistema/g DEO2 + #2ce5 .Sistema/b DEO2 + + ( establecer las coordenadas iniciales x,y ) + #0008 .Pantalla/x DEO2 + #0008 .Pantalla/y DEO2 + + ( dibujar 6 píxeles en una línea horizontal ) + DIBUJAR-PIXEL INC-X + DIBUJAR-PIXEL INC-X + DIBUJAR-PIXEL INC-X + DIBUJAR-PIXEL INC-X + DIBUJAR-PIXEL INC-X + DIBUJAR-PIXEL +``` + +agradable, ¿no? ¡las operaciones ahora se ven más claras! y si quisiéramos tener esta línea disponible para usarla en otras posiciones, podríamos definir una macro para ella: + +``` +%DIBUJAR-LINEA { } ( -- ) +``` + +¡intenta escribiendo la macro y utilizándola en diferentes posiciones de la pantalla! + +# dibujando sprites + +¡ahora, veremos cómo aprovechar el soporte incorporado para "sprites" en el dispositivo de pantalla varvara para dibujar muchos píxeles a la vez! + +el dispositivo de pantalla varvara nos permite utilizar y dibujar tiles de 8x8 píxeles, también llamados sprites. + +hay dos modos posibles: 1bpp (1 bit por píxel), y 2bpp (2 bits por píxel). + +los mosaicos o "tiles" de 1bpp usan dos colores, y se codifican usando 8 bytes; usar un bit por píxel significa que sólo podemos codificar si ese píxel está usando un color, o el otro. + +los tiles de 2bpp utilizan cuatro colores y se codifican utilizando 16 bytes; el uso de dos bits por píxel significa que podemos codificar cuál de los cuatro colores disponibles tiene el píxel. + +almacenaremos y accederemos a estos tiles desde la memoria principal. + +## dibujando sprites de 1bpp + +un tile de 1bpp consiste en un conjunto de 8 bytes que codifican el estado de sus 8x8 píxeles. + +cada byte corresponde a una fila del tile, y cada bit de una fila corresponde al estado de un píxel de izquierda a derecha: puede estar "encendido" (1) o "apagado" (0). + +## codificando un sprite de 1bpp + +por ejemplo, podríamos diseñar un azulejo que corresponda al contorno de un cuadrado de 8x8, activando o desactivando sus píxeles en consecuencia. + +```el contorno de un cuadrado marcado con 1s, y su interior marcado con 0s +11111111 +10000001 +10000001 +10000001 +10000001 +10000001 +10000001 +11111111 +``` + +como cada una de las filas es un byte, podemos codificarlas como números hexadecimales en lugar de binarios. + +vale la pena notar (o recordar) que los grupos de cuatro bits corresponden a un nibble, y cada combinación posible en un nibble puede ser codificada como un dígito hexadecimal. +=> ./hexadecimal.gmi {hexadecimal} + +basándonos en eso, podríamos codificar nuestro cuadrado de la siguiente manera: + +`el contorno de un cuadrado marcado con 1s, y sus interiores marcados con 0s, y su equivalente en hexadecimal +11111111: ff +10000001: 81 +10000001: 81 +10000001: 81 +10000001: 81 +10000001: 81 +10000001: 81 +11111111: ff +``` + +## almacenando el sprite: + +en uxntal, necesitamos etiquetar y escribir en la memoria principal los datos correspondientes al sprite. escribimos los bytes que van de arriba a abajo del sprite: + + +``` +@cuadrado ff81 8181 8181 81ff +``` + +tengamos en cuenta que aquí no estamos utilizando la runa hexadecimal literal (#): queremos utilizar los bytes en bruto en la memoria, y no necesitamos empujarlos hacia abajo en la pila. + +para asegurarse de que estos bytes no son leídos como instrucciones por la cpu uxn, es una buena práctica precederlos con la instrucción BRK: esto interrumpirá la ejecución del programa antes de llegar aquí, dejando a uxn en un estado en el que está esperando entradas. + +## configurando la dirección + +para dibujar el sprite, necesitamos enviar su dirección en memoria al dispositivo de pantalla, y necesitamos asignar un byte de sprite apropiado. + +para lograr esto, escribimos lo siguiente: + +``` +;cuadrado .Pantalla/direc DEO2 +``` + +una nueva runa está aquí! la runa de dirección absoluta literal (;) nos permite empujar hacia abajo en la pila la dirección absoluta de la etiqueta dada en la memoria principal. + +una dirección absoluta tendría 2 bytes de longitud, y se introduce en la pila con LIT2, incluida por el ensamblador cuando se utiliza esta runa. + +como la dirección es de 2 bytes, la imprimimos con DEO2. + +## configurando el color + +de forma similar a lo que ya vimos con el píxel, el envío de un byte a .Pantalla/sprite realizará el dibujo en la pantalla. + +### sprite de nibble alto para 1bpp + +el nibble alto del byte 'sprite' determinará la capa en la que dibujaremos, igual que cuando dibujábamos usando el byte 'pixel'. + +sin embargo, en este caso tendremos otras posibilidades: podemos rotar el sprite en el eje horizontal (x) y/o en el vertical (y). + +los ocho valores posibles de este nibble alto, utilizados para dibujar un sprite de 1bpp, son: + +* 0: dibujar un sprite de 1bpp en el fondo, con la orientación original +* 1: dibujar un sprite de 1bpp en el fondo, rotado horizontalmente +* 2: dibujar un sprite de 1bpp en el fondo, rotado verticalmente +* 3: dibujar un sprite de 1bpp en el fondo, rotado horizontalmente y verticalmente +* 4: dibujar un sprite de 1bpp en primer plano, con la orientación original +* 5: dibujar un sprite de 1bpp en primer plano, rotado horizontalmente +* 6: dibujar un sprite de 1bpp en primer plano, rotado verticalmente +* 7: dibujar un sprite de 1bpp en primer plano, rotado horizontalmente y verticalmente + +si se observa con atención, se puede ver algún patrón: cada bit del nibble alto del byte del sprite corresponde a un aspecto diferente de este comportamiento. + +lo siguiente muestra el significado de cada uno de estos bits en el nibble alto, suponiendo que estamos contando los bits del byte de derecha a izquierda, y de 0 a 7: + +* bit 4: rotación-x +* bit 5: rotación-y +* bit 6: capa (0 es plano de fondo, 1 es primer plano) +* bit 7: modo (0 es 1bpp, 1 es 2bpp) + +como por ejemplo, cuando el nibble alto del 'sprite' es 0, que en binario es 0000, significa que todas las banderas están apagadas: por eso dibuja un sprite de 1bpp (0) en el fondo (0), que no esta rotado ni verticalmente (0) ni horizontalmente (0). + +un nibble alto de 1, es decir, 0001 en binario, tiene la última bandera encendida, por eso se rota horizontalmente, y así sucesivamente. + +### sprite de nibble bajo para 1bpp + +el nibble bajo del byte 'sprite' determinará los colores que se utilizan para dibujar los píxeles "on" y "off" de los tiles. + +* 0: borrar tile +* 1: "encendido" con el color 1, "apagado" con el color 0 +* 2: "encendido" con el color 2, "apagado" con el color 0 +* 3: "on" con el color 3, "off" con el color 0 +* 4: "encendido" con el color 0, "apagado" con el color 1 +* 5: "encendido" con el color 1, "apagado" sin color +* 6: "encendido" con el color 2, "apagado" con el color 1 +* 7: "encendido" con el color 3, "apagado" con el color 1 +* 8: "encendido" con el color 0, "apagado" con el color 2 +* 9: "on" con el color 1, "off" con el color 2 +* a: "on" con color 2, "off" sin color +* b: "encendido" con el color 3, "apagado" con el color 2 +* c: "on" con color 0, "off" con color 3 +* d: "encendido" con el color 1, "apagado" con el color 3 +* e: "on" con el color 2, "off" con el color 3 +* f: "encendido" con el color 3, "apagado" sin color + +notemos que un 0 en el nibble inferior borrará el tile. + +además, 5, 'a' y 'f' en el nibble bajo dibujarán los píxeles que están "encendidos" pero dejarán los que están "apagados" como están: esto le permitirá dibujar sobre algo que ha sido dibujado antes, sin borrarlo completamente. + +##hola sprite + +hagámoslo! El siguiente programa dibujará nuestro sprite una vez: + +``` +( hola-sprite.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 ] + +( programa principal ) +|0100 + ( establecer colores del sistema ) + #2ce9 .Sistema/r DEO2 + #01c0 .Sistema/g DEO2 + #2ce5 .Sistema/b DEO2 + + ( establecer coordenadas x,y ) + #0008 .Pantalla/x DEO2 + #0008 .Pantalla/y DEO2 + + ( establecer la dirección del sprite ) + ;cuadrado .Pantalla/direc DEO2 + + ( dibujar el sprite en el fondo ) + ( usando el color 1 para el contorno ) + #01 .Pantalla/sprite DEO + +BRK + +@cuadrado ff81 8181 8181 81ff +``` + +## hola sprites + +=> ./img/screenshot_uxn-tiles.png captura de pantalla del resultado del programa, mostrando 16 cuadrados coloreados con diferentes combinaciones de contorno y relleno. + +el siguiente código dibujará nuestro sprite cuadrado con las 16 combinaciones de color: + +``` +( hola-sprites.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 ] + +( macros ) +%INIT-X { #0008 .Pantalla/x DEO2 } ( -- ) +%INIT-Y { #0008 .Pantalla/y DEO2 } ( -- ) +%8ADD-X { .Pantalla/x DEI2 #0008 ADD2 .Pantalla/x DEO2 } ( -- ) +%8ADD-Y { .Pantalla/y DEI2 #0008 ADD2 .Pantalla/y DEO2 } ( -- ) + +( programa principal ) +|0100 + ( estableciendo colores de sistema ) + #2ce9 .Sistema/r DEO2 + #01c0 .Sistema/g DEO2 + #2ce5 .Sistema/b DEO2 + + ( estableciendo coordenadas iniciales x,y ) + INIT-X INIT-Y + + ( estableciendo direccion del sprite ) + ;cuadrado .Pantalla/direc DEO2 + + #00 .Pantalla/sprite DEO 8ADD-X + #01 .Pantalla/sprite DEO 8ADD-X + #02 .Pantalla/sprite DEO 8ADD-X + #03 .Pantalla/sprite DEO 8ADD-Y + + INIT-X + #04 .Pantalla/sprite DEO 8ADD-X + #05 .Pantalla/sprite DEO 8ADD-X + #06 .Pantalla/sprite DEO 8ADD-X + #07 .Pantalla/sprite DEO 8ADD-Y + + INIT-X + #08 .Pantalla/sprite DEO 8ADD-X + #09 .Pantalla/sprite DEO 8ADD-X + #0a .Pantalla/sprite DEO 8ADD-X + #0b .Pantalla/sprite DEO 8ADD-Y + + INIT-X + #0c .Pantalla/sprite DEO 8ADD-X + #0d .Pantalla/sprite DEO 8ADD-X + #0e .Pantalla/sprite DEO 8ADD-X + #0f .Pantalla/sprite DEO + +BRK + +@cuadrado ff81 8181 8181 81ff +``` + +observemos que en este caso, tenemos un par de macros 8ADD-X y 8ADD-Y para incrementar cada coordenada por 0008: ese es el tamaño del tile. + +## experimentos de rotación + +como el sprite cuadrado es simétrico, no podemos ver el efecto de rotarlo. + +aquí están los sprites de la roca y del personaje de darena: +=> ./darena.gmi darena + +``` +@piedra 3c4e 9ffd f962 3c00 +@personaje 3c7e 5a7f 1b3c 5a18 +``` + +te invito a que intentes usar estos sprites para explorar cómo dibujarlos rotados en diferentes direcciones. + +## dibujando sprites de 2bpp + +en los sprites de 2bpp cada píxel puede tener uno de los cuatro colores posibles. + +podemos pensar que, para asignar estos colores, codificaremos uno de los cuatro estados en cada uno de los píxeles del sprite. + +cada uno de estos estados puede codificarse con una combinación de dos bits. a estos estados se les puede asignar diferentes combinaciones de los cuatro colores del sistema utilizando los valores apropiados en el byte 'sprite' de la pantalla. + +un solo tile de 2bpp de 8x8 píxeles necesita 16 bytes para ser codificada. estos bytes se ordenan según un formato llamado chr. + +## codificando un sprite de 2bpp + +para demostrar esta codificación, vamos a remezclar nuestro cuadrado de 8x8, asignando uno de los cuatro estados posibles (0, 1, 2, 3) a cada uno de los píxeles: + +```un cuadrado de 8x8 construido con los dígitos 0 y 1 en el borde, y 2 y 3 en el interior +00000001 +03333311 +03333211 +03332211 +03322211 +03222211 +01111111 +11111111 +``` + +podemos pensar en cada uno de estos dígitos como un par de bits: 0 es 00, 1 es 01, 2 es 10 y 3 es 11. + +de esta manera, podríamos pensar en nuestro sprite de la siguiente manera: + +```un cuadrado de 8x8 construido con un par de bits entre paréntesis, correspondientes a la representación binaria de cada uno de los estados +(00) (00) (00) (00) (00) (00) (00) (01) +(00) (11) (11) (11) (11) (11) (01) (01) +(00) (11) (11) (11) (11) (10) (01) (01) +(00) (11) (11) (11) (10) (10) (01) (01) +(00) (11) (11) (10) (10) (10) (01) (01) +(00) (11) (10) (10) (10) (10) (01) (01) +(00) (01) (01) (01) (01) (01) (01) (01) +(01) (01) (01) (01) (01) (01) (01) (01) +``` + +la codificación chr requiere una interesante manipulación de esos bits: podemos pensar que cada par de bits tiene un bit alto en la izquierda y un bit bajo en la derecha. + +separamos nuestro tile en dos cuadrados diferentes, uno para los bits altos y otro para los bits bajos: + +``` dos cuadrados de 8x8 correspondientes a la división del cuadrado anterior en sus bits altos y bajos +00000000 00000001 +01111100 01111111 +01111100 01111011 +01111100 01110011 +01111100 01100011 +01111100 01000011 +00000000 01111111 +00000000 11111111 +``` + +ahora podemos pensar en cada uno de estos cuadrados como sprites de 1bpp, y codificarlos en hexadecimal como lo hicimos antes: + +los dos cuadrados 8x8 anteriores con su correspondiente codificación hexadecimal +00000000: 00 00000001: 01 +01111100: 7c 01111111: 7f +01111100: 7c 01111011: 7b +01111100: 7c 01110011: 73 +01111100: 7c 01100011: 63 +01111100: 7c 01000011: 43 +00000000: 00 01111111: 7f +00000000: 00 11111111: ff +``` + +## almacenando el sprite + +para escribir este sprite en la memoria, primero almacenamos el cuadrado correspondiente a los bits bajos, y luego el cuadrado correspondiente a los bits altos. cada uno de ellos, de arriba a abajo: + +``` +@nuevo-cuadrado 017f 7b73 6343 7fff 007c 7c7c 7c7c 0000 +``` + +podemos establecer esta dirección en el dispositivo de pantalla igual que antes: + +``` +;nuevo-cuadrado .Pantalla/direc DEO2 +``` + +el dispositivo de pantalla tratará esta dirección como un sprite 2bpp cuando usemos el byte de color apropiado. + +## configurando el color + +¡veamos cómo utilizar el sprite byte para dibujar tildes de 2bpp! + +### sprite de nibble alto para 2bpp + +el nibble alto para los sprites de 2bpp nos permitirá elegir la capa que queremos que se dibuje, y la dirección de rotación. + +los ocho valores posibles para este nibble son: + +* 8: dibujar un sprite de 2bpp en el fondo, con la orientación original +* 9: dibujar un sprite de 2bpp en el fondo, rotado horizontalmente +* a: dibujar un sprite 2bpp en el fondo, rotado verticalmente +* b: dibujar un sprite de 2bpp en el fondo, rotado horizontal y verticalmente +* c: dibujar un sprite de 2bpp en primer plano, con la orientación original +* d: dibujar un sprite de 2bpp en primer plano, rotado horizontalmente +* e: dibujar un sprite de 2bpp en primer plano, rotado verticalmente +* f: dibujar un sprite de 2bpp en primer plano, rotado horizontal y verticalmente + +notemos que estos ocho valores tienen todos un bit más a la izquierda en 1: este bit señala que vamos a dibujar un sprite de 2bpp. los otros tres bits del nibble se comportan como se ha descrito anteriormente en el caso de 1bpp. + +### sprite de nibble bajo para 2bpp + +el nibble bajo nos permitirá elegir entre muchas combinaciones de colores asignados a cada uno de los diferentes estados de los píxeles: + +* 0: colores 0, 0, 1, 2 +* 1: colores 0, 1, 2, 3 +* 2: colores 0, 2, 3, 1 +* 3: colores 0, 3, 1, 2 +* 4: colores 1, 0, 1, 2 +* 5: colores ninguno, 1, 2, 3 +* 6: colores 1, 2, 3, 1 +* 7: colores 1, 3, 1, 2 +* 8: colores 2, 0, 1, 2 +* 9: colores 2, 1, 2, 3 +* a: colores ninguno, 2, 3, 1 +* b: colores 2, 3, 1, 2 +* c: colores 3, 0, 1, 2 +* d: colores 3, 1, 2, 3 +* e: colores 3, 2, 3, 1 +* f: colores ninguno, 3, 1, 2 + +## hola nuevos sprites! + +=> ./img/screenshot_uxn-tiles-2bpp.png captura de pantalla de la salida del programa, mostrando 16 cuadrados coloreados con diferentes combinaciones de contorno y relleno. + +el siguiente código mostrará nuestro sprite en las 16 diferentes combinaciones de color. hay un poco de margen entre las baldosas para poder apreciarlas mejor: + +``` +( hola-sprite-2bpp.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 ] + +( macros ) +%INIT-X { #0008 .Pantalla/x DEO2 } ( -- ) +%INIT-Y { #0008 .Pantalla/y DEO2 } ( -- ) +%cADD-X { .Pantalla/x DEI2 #000c ADD2 .Pantalla/x DEO2 } ( -- ) +%cADD-Y { .Pantalla/y DEI2 #000c ADD2 .Pantalla/y DEO2 } ( -- ) + +( programa principal ) +|0100 + ( estableciendo colores de sistema ) + #2ce9 .Sistema/r DEO2 + #01c0 .Sistema/g DEO2 + #2ce5 .Sistema/b DEO2 + + ( estableciendo coordenadas iniciales x,y ) + INIT-X INIT-Y + ( set sprite direcess ) + ;nuevo-cuadrado .Pantalla/direc DEO2 + + #80 .Pantalla/sprite DEO cADD-X + #81 .Pantalla/sprite DEO cADD-X + #82 .Pantalla/sprite DEO cADD-X + #83 .Pantalla/sprite DEO cADD-Y + + INIT-X + #84 .Pantalla/sprite DEO cADD-X + #85 .Pantalla/sprite DEO cADD-X + #86 .Pantalla/sprite DEO cADD-X + #87 .Pantalla/sprite DEO cADD-Y + + INIT-X + #88 .Pantalla/sprite DEO cADD-X + #89 .Pantalla/sprite DEO cADD-X + #8a .Pantalla/sprite DEO cADD-X + #8b .Pantalla/sprite DEO cADD-Y + + INIT-X + #8c .Pantalla/sprite DEO cADD-X + #8d .Pantalla/sprite DEO cADD-X + #8e .Pantalla/sprite DEO cADD-X + #8f .Pantalla/sprite DEO + +BRK + +@nuevo-cuadrado 017f 7b73 6343 7fff 007c 7c7c 7c7c 0000 +``` + +intenta rotar los tiles! + +## screen.tal y las combinaciones del byte del sprite + +el ejemplo screen.tal en el repo de uxn consiste en una tabla que muestra todas las posibles (¡256!) combinaciones de nibbles altos y bajos en el byte del sprite. + +=> ./img/screenshot_uxn-screen.png captura de pantalla del ejemplo screen.tal, que muestra un sprite coloreado y volteado de diferentes maneras. + +=> https://git.sr.ht/~rabbits/uxn/tree/main/item/projects/examples/devices/screen.tal código de screen.tal + +comparémoslo con todo lo que hemos dicho sobre el byte "sprite". + +## diseñando sprites + +nasu es una herramienta de 100R, escrita en uxntal, que facilita el diseño y la exportación de sprites 2bpp. + +=> https://100r.co/site/nasu.html 100R - nasu + +además de usarlo para dibujar con los colores 1, 2, 3 (y borrar para obtener el color 0), puedes usarlo para encontrar los colores de tu sistema, para ver cómo se verán tus sprites con los diferentes modos de color (también conocidos como modos de mezcla), y para ensamblar objetos hechos de múltiples sprites. + +puedes exportar e importar archivos chr, que puedes incluir en tu código usando una herramienta como hexdump. + +¡te recomiendo que lo pruebes! + +# tamaño de la pantalla y capacidad de respuesta + +lo último que cubriremos hoy tiene que ver con las suposiciones que hace varvara sobre el tamaño de su pantalla, y algunas estrategias de código que podemos usar para lidiar con ellas. + +en resumen, ¡no hay un tamaño de pantalla estándar! + +por defecto, la pantalla del emulador varvara tiene un tamaño de 512x320 píxeles (o 64x40 tiles). + +sin embargo, y a modo de ejemplo, el ordenador virtual también funciona en la nintendo ds, con una resolución de 256x192 píxeles (32x24 tiles), y en el teletipo con una resolución de 128x64 píxeles (16x8 tiles) + +como programadorxs, se espera que decidamos qué hacer con ellos: nuestros programas pueden adaptarse a los distintos tamaños de pantalla, pueden tener distintos modos según el tamaño de la pantalla, etc. + +## cambiando el tamaño de la pantalla + +adicionalmente, podemos cambiar el tamaño de la pantalla varvara escribiendo en los puertos .Pantalla/ancho y .Pantalla/alto. + +por ejemplo, el siguiente código cambiaría la pantalla a una resolución de 640x480: + +``` +#0280 .Pantalla/ancho DEO2 ( anchura de 640 ) +#01e0 .Pantalla/alto DEO2 ( altura de 480 ) +``` + +tengamos en cuenta que esto sólo funcionaría para las instancias del emulador varvara en las que el tamaño de la pantalla puede cambiarse realmente, por ejemplo, porque la pantalla virtual es una ventana. + +¡sería importante tener en cuenta los aspectos de la capacidad de respuesta que se discuten a continuación, para los casos en los que no podemos cambiar el tamaño de la pantalla! + +### tamaño de pantalla por defecto + +originalmente, la forma de cambiar el tamaño de la pantalla en uxnemu implicaba editar su código fuente. + +si te has descargado el repositorio con el código fuente, verás que dentro del directorio src/ hay un uxnemu.c, con un par de líneas parecidas a las siguientes: + +``` +#define WIDTH 64 * 8 +#define HEIGHT 40 * 8 +``` + +esos dos números, 64 y 40, son el tamaño de pantalla por defecto en tiles, como mencionamos anteriormente. + +puedes cambiarlos, guardar el archivo y volver a ejecutar el script build.sh para que uxnemu funcione con esta nueva resolución. + +## leeyendo y adaptando el tamaño de la pantalla (lo básico) + +como recordarás de los puertos de dispositivos de pantalla mencionados anteriormente, la pantalla nos permite leer su anchura y altura como cortos. + +si quisiéramos, por ejemplo, dibujar un píxel en el centro de la pantalla independientemente del tamaño de la misma, podemos traducir a uxntal una expresión como la siguiente: + +``` +x = anchopantalla/2 +y = altopantalla/2 +``` + +### división uxntal + +para esto, vamos a introducir las instrucciones MUL y DIV: funcionan como ADD y SUB, pero para la multiplicación y la división: + +* MUL: toma los dos elementos superiores de la pila, los multiplica y empuja el resultado ( a b -- a*b ) +* DIV: toma los dos elementos superiores de la pila, los divide, y empuja el resultado ( a b -- a/b ) + +usando DIV, nuestra expresión traducida para el caso de la coordenada x, podría verse como: + +``` +.Pantalla/ancho DEI2 ( obtener el ancho de la pantalla en la pila ) +#0002 DIV2 ( dividir sobre 2 ) +.Pantalla/x DEO2 ( tomar el resultado de la pila y enviarlo a .Pantalla/x ) +``` + +### desplazamiento de bits + +si lo que queremos es dividir por encima o multiplicar por potencias de dos (como en este caso), también podemos utilizar la instrucción SFT. + +esta instrucción toma un número y un "valor de desplazamiento" que indica la cantidad de posiciones de bit a desplazar a la derecha, y/o a la izquierda. + +el nibble inferior del valor de desplazamiento indica a uxn cuántas posiciones hay que desplazar a la derecha, y el nibble superior expresa cuántos bits hay que desplazar a la izquierda. + +para dividir un número por encima de 2, tendríamos que desplazar sus bits un espacio a la derecha. + +por ejemplo, dividir 10 (en decimal) entre 2 podría expresarse de la siguiente manera: + +``` +#0a #01 SFT ( resultado: 05 ) +``` + +0a es 0000 1010 en binario, y 05 es 0000 0101 en binario: los bits de 0a se desplazaron una posición a la derecha, y se introdujo un cero como bit más a la izquierda. + +para multiplicar por 2, desplazamos un espacio a la izquierda: + +``` +#0a #10 SFT ( resultado: 14 en hexadecimal ) +``` + +14 en hexadecimal (20 en decimal), es 0001 0100 en binario: los bits de 0a fueron desplazados una posición a la izquierda, y un cero fue introducido como el bit más a la derecha. + +en modo corto, el número a desplazar es un corto, pero el valor de desplazamiento sigue siendo un byte. + +por ejemplo, lo siguiente dividirá el ancho de la pantalla en dos, utilizando el desplazamiento a nivel de bits: + +``` +.Pantalla/ancho DEI2 +#01 SFT2 +``` + +### macros HALF + +para seguir ilustrando el uso de las macros, podríamos definir unas macros HALF y HALF2, utilizando DIV o SFT. + +usando DIV: + +``` +%HALF { #02 DIV } ( numero -- numero/2 ) +%HALF2 { #0002 DIV2 } ( numero -- numero/2 ) +``` + +usando SFT: + +``` +%HALF { #01 SFT } ( numero -- numero/2 ) +%HALF2 { #01 SFT2 } ( numero -- numero/2 ) +``` + +and use any of them to calculate the center: + +``` +.Pantalla/ancho DEI2 HALF2 .Pantalla/x DEO2 +.Pantalla/alto DEI2 HALF2 .Pantalla/y DEO2 +``` + +notemos que la macro HALF2 que utiliza SFT2 necesitará un byte menos que la que utiliza DIV2. esto puede o no ser importante dependiendo de tus prioridades :) + +## dibujando sprites en posiciones específicas + +como ejercicio para ti, te invito a que escribas el código que lograría algo o todo lo siguiente: + +* dibuja un tile de 8x8 completamente centrado en la pantalla +* dibujar un tile de 8x8 en cada una de las esquinas de la pantalla +* dibujar un tile de 8x8 tocando cada uno de los bordes de la pantalla, centrados en cada uno de ellos + +una vez que lo logres, te invito a que hagas lo mismo, pero utilizando una imagen compuesta por múltiples tiles (por ejemplo, tiles de 2x2, tiles de 1x2, etc). + +# instrucciones del día 2 + +además de cubrir los fundamentos del dispositivo de pantalla hoy, discutimos estas nuevas instrucciones: + +* DEI: lee un valor en la pila, desde la dirección del dispositivo dada en la pila ( dirección -- valor ) +* INC: incrementa el valor en la parte superior de la pila ( a -- a+1 ) +* BRK: rompe el flujo del programa, para cerrar subrutinas +* MUL: toma los dos primeros elementos de la pila, los multiplica y empuja el resultado ( a b -- a*b ) +* DIV: toma los dos primeros elementos de la pila, los divide, y empuja el resultado ( a b -- a/b ) +* SFT: toma un valor de desplazamiento y un número a desplazar con ese valor, y lo desplaza. el nibble inferior del valor de desplazamiento indica el desplazamiento a la derecha, y el nibble superior el desplazamiento a la izquierda ( número desplazo -- númerodesplazado ) + +también cubrimos el modo corto, que le indica a la cpu que debe operar con palabras de 2 bytes de longitud. + +# día 3 + +¡en el tutorial de uxn del día 3 empezamos a trabajar con la interactividad usando el teclado, y cubrimos en profundidad varias instrucciones uxntales! + +=> ./tutorial_de_uxn_día_3.gmi {tutorial de uxn día 3} + +sin embargo, te invito a que te tomes un descanso, y a que sigas explorando el dibujo en la pantalla de uxn a través del código antes de continuar! + +# apoyo + +si te ha gustado este tutorial y te ha resultado útil, considera compartirlo y darle tu apoyo :) + +=> ./apoyo.gmi {apoyo} \ No newline at end of file diff --git a/src/tutorial_de_uxn_día_3.gmo b/src/tutorial_de_uxn_día_3.gmo index 18cf604..0cbedaa 100644 --- a/src/tutorial_de_uxn_día_3.gmo +++ b/src/tutorial_de_uxn_día_3.gmo @@ -1,3 +1,773 @@ -# tutorial uxn: día 3 +# tutorial de uxn: día 3, saltos condicionales y el teclado/controlador -traducción en proceso +¡esta es la tercera sección del tutorial uxn! +=> ./tutorial_de_uxn.gmi {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 + +* 0: Ctrl (botón A) +* 1: Alt (botón B) +* 2: Shift (botón de selección) +* 3: Esc (botón de inicio) +* 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 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 uxntales 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 'falso0: + +``` +.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 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 EOR 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: 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 -- ) + +en modo byte, las direcciones que utilizan estas instrucciones son de un byte. + +estas direcciones de un byte son relativas y con signo: 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 literal 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 + + ( dibujar 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 en consecuencia si se pulsan 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 PRINT-DIGIT 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: + +* POP: Eliminar el elemento superior de la pila ( a -- ) +* DUP: Duplicar; empujar una copia del elemento superior ( a -- a a ) +* SWP: 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: + +``` +ASCII-DIGIT { DUP #2f GTH SWP #3a LTH AND } ( byte -- bandera ) +``` + +y utilizarla con el byte que queramos: + +``` +#30 ?ASCII-DIGIT ( empuja 01 hacia abajo en la pila ) +#20 ?ASCII-DIGIT ( empuja 00 hacia abajo en la pila ) +.Controlador/tecla DEI ?ASCII-DIGIT ( 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, pondremos 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 de arriba? + +``` + 0001 0000 ( botón ) +AND 0001 0000 ( máscara ) + ---------- + 0001 0000 ( resultado ) +``` + +¿y si se pulsan tanto Up 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 apilamiento. + +``` +( 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 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-filas JMP ( continuar con la comprobación de las filas ) + + &relleno + #04 .Pantalla/sprite DEO ( dibujar relleno ) + + &comprobar-filas + ( 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 ( incremento 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 ( incremento 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 :) + +# posibilidades de práctica + +¡aquí tienes otras ideas para que practiques con lo que hemos tratado 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 fichas 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 movimiento interactivo suave 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 uxntales 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: Quitar el elemento superior de la pila ( a -- ) +* DUP: Duplicar; empujar una copia del elemento superior ( a -- a a ) +* SWP: Intercambio; cambia el orden de los dos primeros elementos de la pila ( a b -- b a ) +* 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 uxn día 4 cubrimos el uso del vector de pantalla para crear animaciones, ya sean interactivas o no. + +=> ./tutorial_de_uxn_día_4.gmi {tutorial de uxn día 4} + +¡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 :) + +=> ./apoyo.gmi {apoyo} \ No newline at end of file diff --git a/src/tutorial_de_uxn_día_4.gmo b/src/tutorial_de_uxn_día_4.gmo new file mode 100644 index 0000000..0d683e1 --- /dev/null +++ b/src/tutorial_de_uxn_día_4.gmo @@ -0,0 +1,969 @@ +# tutorial uxn: día 4, variables y bucle de animación + +¡esta es la cuarta sección del tutorial uxn! +=> ./tutorial_de_uxn {tutorial de uxn} + +aquí hablamos del bucle de animación del ordenador varvara, a través de su vector de dispositivo de pantalla. + +también hablamos del uso de la memoria del programa como un espacio para datos usando "variables". esto nos permite guardar y recuperar datos durante el tiempo de ejecución de nuestros programas, y puede ahorrarnos complejas luchas en la pila :) + +# el vector pantalla + +discutimos el dispositivo de pantalla de varvara en el día 2, pero nos saltamos su puerto vectorial para centrarnos en cómo dibujar con él: + +``` +|20 @Pantalla [ &vector $2 &ancho $2 &alto $2 &pad $2 &x $2 &y $2 &direc $2 &pixel $1 &sprite $1 ] +``` + +ahora que ya tenemos el concepto de vectores de dispositivos en el tutorial de uxn día 3, ¡vamos a entrar de lleno en cómo usar el de la pantalla! +=> ./tutorial_de_uxn_día_3.gmi {tutorial de uxn día 3} + +## asignación + +la siguiente línea de uxntal asignaría la dirección absoluta de la etiqueta en-cuadro al vector pantalla: + +``` +;en-cuadro .Pantalla/vector DEO2 +``` +uxn saltará a la ubicación de la etiqueta a un ritmo de 60 veces por segundo: podemos utilizar la subrutina bajo en-cuadro para cambiar el contenido de la pantalla, generando animación, y/o también podemos utilizarla para otros propósitos relacionados con la temporización. + +## una línea que crece + +el siguiente programa demuestra un uso básico pero potente del vector pantalla: en cada fotograma, dibuja un píxel en las coordenadas x,y de la pantalla dadas, y añade 1 al valor de la coordenada x: + +``` +( hola-linea.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 ] + +( init ) +|0100 + ( establecer colores del sistema ) + #2ce9 .Sistema/r DEO2 + #01c0 .Sistema/g DEO2 + #2ce5 .Sistema/b DEO2 + + ( establecer coordenadas iniciales x,y ) + #0008 .Pantalla/x DEO2 + #0008 .Pantalla/y DEO2 + + ( establecer vector de pantalla ) + ;en-cuadro .Pantalla/vector DEO2 +BRK + +@en-cuadro ( -> ) + ( dibujar un pixel en el fondo con el color 1 ) + #01 .Pantalla/pixel DEO + + ( incrementar Pantalla/x ) + .Pantalla/x DEI2 INC2 .Pantalla/x DEO2 +BRK +``` + +nota que el código es muy similar al que escribimos el día 2 para dibujar una línea. + +en ese, incrementamos manualmente el valor de Pantalla/x para dibujar 6 píxeles. + +aquí, el código para incrementar Pantalla/x es llamado dentro de la subrutina en-cuadro en su lugar, haciendo que ocurra 60 veces por segundo. + +## posibilidades de crecimiento + +estos son algunos cambios para que los pruebes y practiques: + +* ¿cómo harías que la línea creciera en vertical? y en diagonal? +* ¿cómo harías que la línea creciera en sentido contrario? +* ¿Cómo harías que la línea fuera punteada? +* ¿Cómo harías que la línea dejara de crecer en una determinada posición? (¡recuerda los saltos condicionales!) +* ¿cómo dibujarías utilizando un sprite en lugar de un píxel? + +# variables + +¡el vector de pantalla varvara abre todo un mundo de posibilidades! + +merece señalar que muchas de estas posibilidades requieren formas de almacenar y recuperar datos entre fotogramas. + +en el ejemplo anterior, estamos usando los puertos de pantalla para las coordenadas x e y como una forma de almacenar las coordenadas del pixel. + +pero ¿qué sucede cuando queremos dibujar diferentes objetos, cada uno con su propio conjunto de coordenadas y otras características que pueden cambiar con el tiempo? + +¡podemos utilizar etiquetas en la memoria del programa para conseguirlo! + +## variables con direcciones absolutas + +en cierto modo, al almacenar los datos de nuestros sprites ya hemos hecho algo así. + +hemos etiquetado una sección de memoria con contenidos que no son instrucciones para uxn, sino datos; por ejemplo: + +``` +@cuadrado ff81 8181 8181 81ff +``` + +sin embargo, no hemos utilizado los datos directamente; hemos enviado su dirección al puerto del dispositivo de pantalla correspondiente. + +### etiquetas + +podríamos utilizar un sistema similar para almacenar, por ejemplo, las coordenadas x e y en lugar de los datos del sprite: + +``` +@pixel-x 0008 +@pixel-y 0008 +``` + +o si no quisiéramos iniciarlas aquí, podríamos definirlas de la siguiente manera: + +``` +@pixel-x $2 +@pixel-y $2 +``` +recuerda que $2 crea un pad relativo de dos bytes: esto hace que pixel-y sea una etiqueta para una dirección en memoria dos bytes después de pixel-x. y cualquier código posterior ocurrirá dos bytes después de pixel-y. + + +también podríamos usar etiquetas y sub-etiquetas, de manera muy similar a como definimos los dispositivos y sus puertos: + +``` +@pixel [ &x $2 &y $2 ] +``` + +### instrucciones: LDA y STA + +¿cómo podríamos leer (cargar) y escribir (almacenar) el contenido de la memoria en esas etiquetas? + +aquí están las dos instrucciones que nos ayudarían: + +* LDA: carga y empuja hacia abajo en la pila el valor en la dirección absoluta dada ( dirección -- valor ) +* STA: almacena en la dirección absoluta dada el valor dado ( dirección valor -- ) + +como ya hemos comentado, una dirección absoluta siempre tendrá una longitud de dos bytes. + +en el modo corto, LDA2 cargará un corto desde la dirección dada, y STA2 almacenará un corto en la dirección dada. + +### ejemplos + +como ejemplo, el siguiente código leería los dos bytes de pixel/x, los incrementaría en uno, y los almacenaría de nuevo en pixel/x: + +``` +;pixel/x LDA2 ( cargar pixel/x en la pila ) +INC2 ( incrementar ) +;pixel/x STA2 ( almacenar el resultado en pixel/x ) + +BRK + +@pixel [ &x $2 &y $2 ] +``` + +nótese el uso de BRK antes de la etiqueta del píxel para que uxn se detenga antes de leer los datos como instrucciones. + +lo siguiente es una variación que también duplica el nuevo valor de pixel/x para enviarlo a Pantalla/x: + +``` +;pixel/x LDA2 ( cargar pixel/x en la pila ) +INC2 ( incrementar ) +DUP2 ( duplicar el resultado ) +;pantalla/x DEO2 ( establecer como pantalla/x ) +;pixel/x STA2 ( y guardar el resultado en pixel/x ) + +BRK + +@pixel [ &x $2 &y $2 ] +``` + +nótese que podríamos haber conseguido el mismo resultado almacenando el resultado, y luego recargándolo y enviándolo como salida. + +aquí podemos ver cómo un DUP2 puede facilitar esa operación, siempre y cuando mantengamos un modelo mental (¡o tangible!) de lo que ocurre en la pila. + +### valores iniciales + +una posible ventaja de utilizar direcciones absolutas es que podemos iniciar el contenido de nuestras variables en el momento de ensamblaje, por ejemplo: + +``` +@pixel [ &x 0008 &y 0008 ] +``` + +estos contenidos iniciales cambiarán cada vez que usemos una instrucción STA allí :) + +## variables en la página cero + +las variables con dirección absoluta funcionan bien para los casos en los que queremos poder acceder a su contenido desde cualquier parte de nuestro programa (es decir, "variables globales"). + +sin embargo, uxn tiene un mecanismo mejor para esos casos: ¡la página cero! + +como recordará, la página cero consiste en las primeras 256 direcciones de la memoria del programa. normalmente, un programa comienza en la dirección 0100, que es la siguiente dirección después de la página cero. + +podemos referirnos a cualquiera de las 256 direcciones de la página cero utilizando un solo byte, en lugar de los dos bytes que se necesitan para las direcciones absolutas. + +algo importante a tener en cuenta es que el contenido de la página cero no está presente en las roms uxn. + +esto significa que una salvedad de usar variables allí, es que para iniciarlas necesitamos hacerlo durante el tiempo de ejecución, almacenando valores de la pila en ellas. + +### etiquetas en la página cero + +las etiquetas para la página cero funcionarían igual que antes; sólo tenemos que especificar que están en la página cero con una almohadilla absoluta: + +``` +|0000 ( página cero ) +@pixel [ &x $2 &y $2 ] +``` + +para referirnos a ellas, utilizaríamos la runa punto (.) para las direcciones literales de página cero, en lugar de la runa dos puntos (;) para las direcciones literales absolutas. + +### instrucciones: LDZ, STZ + +las instrucciones para cargar (leer) y almacenar (escribir) desde y hacia la página cero son: + +* LDZ: carga y empuja hacia abajo en la pila el valor en la dirección de la página cero dada ( dirección -- valor ) +* STZ: almacena en la dirección de la página cero el valor dado ( dirección del valor -- ) + +en estas instrucciones, la dirección siempre será de un byte. + +en el modo corto, LDZ2 cargará un corto desde la dirección dada, y STZ2 almacenará un corto en la dirección dada. + +### ejemplos + +el siguiente ejemplo consiste en la misma línea que crece, pero ahora utilizando la página cero para almacenar las coordenadas x e y del píxel en lugar de los puertos x e y de la pantalla. + +en este caso el programa es más largo, pero puede ser visto como una buena plantilla para tener otras líneas que se comporten de diferentes maneras: + +``` +( hola-linea.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 ] + +( página cero ) +|0000 +@pixel [ &x $2 &y $2 ] + +( init ) +|0100 + ( establecer colores del sistema ) + #2ce9 .Sistema/r DEO2 + #01c0 .Sistema/g DEO2 + #2ce5 .Sistema/b DEO2 + + ( establecer coordenadas iniciales x,y ) + #0008 .pixel/x STZ2 + #0008 .pixel/y STZ2 + + ( establecer vector de pantalla ) + ;en-cuadro .Pantalla/vector DEO2 +BRK + +@en-cuadro ( -> ) + ( cargar las coordenadas x,y de la página cero y enviarlas a la pantalla ) + .pixel/x LDZ2 .Pantalla/x DEO2 + .pixel/y LDZ2 .Pantalla/y DEO2 + + ( dibujar un pixel en el fondo con color 1 ) + #01 .Pantalla/pixel DEO + + ( incrementar el pixel/x ) + .pixel/x LDZ2 INC2 .pixel/x STZ2 +BRK +``` + +notemos el uso de la runa literal de dirección de página cero (.) para referirse a la etiqueta .pixel. + +además, observe que en el caso de .pixel la dirección se refiere a la página cero, a la que se accede con LDZ/STZ, y en el caso de .Pantalla la dirección se refiere al espacio de direcciones entrada/salida (i/o), al que se accede con DEO/DEI. + +### un poco de práctica en la pila + +nota que las siguientes instrucciones también incrementarían .pixel/x, pero estableciendo su dirección sólo una vez: + +``` +.pixel/x DUP LDZ2 INC2 ROT STZ2 +``` + +te recomiendo que sigas cómo cambia el contenido de la pila en cada uno de los siguientes pasos: ten en cuenta que algunas de las instrucciones están en modo corto :) + +esta línea de código contiene la misma cantidad de bytes que la anterior. + +una posible desventaja es que podría ser menos legible. pero una posible ventaja es que podría convertirse en una macro: + +``` +( incrementar un short desde la página cero ) +%ZP-INC2 { DUP LDZ2 INC2 ROT STZ2 } ( zp-dirección -- ) +``` + +## variables en direcciones relativas + +otra posibilidad que tenemos en uxn y que podría ser más apropiada para las "variables locales", consiste en utilizar direcciones relativas. + +de forma similar a las variables de la página cero, para direccionar estas variables sólo necesitamos un byte. + +sin embargo, como estas direcciones se dan como offsets relativos y con signo, sólo se pueden alcanzar si están dentro de los 256 bytes que rodean a la instrucción que las carga o almacena. + +instrucciones ###: LDR, STR + +las instrucciones para trabajar de esta manera son: + +* LDR: carga y empuja hacia abajo en la pila el valor en la dirección relativa dada ( dirección -- valor ) +* STR: almacena en la dirección relativa dada el valor dado ( dirección valor -- ) + +similar a LDZ y STZ, en estas instrucciones la dirección siempre será de un byte. + +en el modo corto, LDR2 cargará un corto desde la dirección dada, y STR2 almacenará un corto en la dirección dada. + +### ejemplos + +lo siguiente es la subrutina en-cuadro que dibuja la línea creciente, pero almacenando las coordenadas de los píxeles en una variable "local" a la que se accede mediante LDR y STR. + +``` +@en-cuadro ( -> ) + ( carga las coordenadas x,y desde la página cero y las envía a la pantalla ) + ,pixel/x LDR2 .Pantalla/x DEO2 + ,pixel/y LDR2 .Pantalla/y DEO2 + + ( dibujar un pixel en el fondo con el color 1 ) + #01 .Pantalla/pixel DEO + + ( incrementa pixel/x ) + ,pixel/x LDR2 INC2 ,pixel/x STR2 +BRK + +@pixel [ &x $2 &y $2 ] +``` + +nótese el uso de la runa coma (,) para indicar que es una dirección relativa; uxnasm calcula el offset requerido asumiendo que será utilizado en la siguiente instrucción. + +en este caso realmente no podemos duplicar ese offset como hicimos anteriormente con la dirección de página cero, porque es específica de la posición en el código en que fue escrita. + +si declaráramos estas variables como sub-etiquetas de en-cuadro, el código quedaría como sigue: + +``` +@en-cuadro ( -> ) + ( carga las coordenadas x,y de la página cero y las envía a la pantalla ) + ,&pixel-x LDR2 .Pantalla/x DEO2 + ,&pixel-y LDR2 .Pantalla/y DEO2 + + ( dibujar un pixel en el fondo con el color 1 ) + #01 .Pantalla/pixel DEO + + ( incrementa pixel/x ) + ,&pixel-x LDR2 INC2 ,&pixel-x STR2 +BRK +( variables locales en-cuadro ) + &pixel-x $2 &pixel-y $2 +``` + +observe que en este caso, la runa de la coma (,) va acompañada de la runa de la sub-etiqueta (&). + +el uso de este tipo de variables tendrá más sentido en el día 5 del tutorial :) + +# cambio de posición del sprite + +el uso de "variables" nos ayudará ahora a discutir tres formas diferentes de animar un sprite: + +* cambio de posición autónomo +* cambio de posición interactivo (con el teclado) +* cambio autónomo de la tile dibujada + +los revisaremos por separado para mantener los ejemplos relativamente simples y legibles. + +tenga en cuenta que estos ejemplos también sirven para discutir más posibilidades de programación uxntal, y pueden llegar a ser un poco abrumadores. + +te recomiendo que revises y experimentes con uno a la vez, pacientemente :) + +## cambio de posición autónomo + +ya discutimos como hacer que uxn cambie la posición de un pixel en la pantalla, dejando un rastro. + +cambiar ese programa para dibujar un sprite de 8x8 en su lugar sería relativamente sencillo, y puede que ya lo hayas probado: tendríamos que usar Pantalla/sprite en lugar de Pantalla/pixel, con un byte apropiado para definir su color y orientación, y tendríamos que establecer la dirección de los datos de nuestro sprite en Pantalla/direc. + +eso daría como resultado un sprite que se mueve y que además deja un rastro: ¡te invito a que lo pruebes primero! + +### sin rastro + +ok, eso puede ser útil en algunos casos, pero ¿cómo podemos evitar dejar el rastro? + +una posible forma de conseguirlo sería siguiendo este orden de operaciones dentro de la subrutina en-cuadro: + +* borrar el sprite previamente dibujado +* cambiar de posición +* dibujar nuevo sprite + +esto nos permite borrar el sprite de su posición en el fotograma anterior, actualizar sus coordenadas a una nueva posición, y luego dibujarlo ahí. + +### código de ejemplo + +el siguiente programa ilustra los puntos anteriores, haciendo que nuestro cuadrado del día 2 se desplace de izquierda a derecha en el centro de nuestra pantalla. + +¡combina varias cosas que hemos cubierto en los últimos días! + +``` +( hola-sprite-animado.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 ] + +( macros/constantes ) +%HALF2 { #01 SFT2 } ( desplazar un bit a la derecha ) ( corto -- corto/2 ) +%color-borrar { #40 } ( borrar 1bpp sprite del primer plano ) +%color-2 { #4a } ( dibujar sprite de 1bpp con color 2 y transparencia ) + +( página cero ) +|0000 +@sprite [ &pos-x $2 &pos-y $2 ] + +( init ) +|0100 + ( establecer colores del sistema ) + #2ce9 .Sistema/r DEO2 + #01c0 .Sistema/g DEO2 + #2ce5 .Sistema/b DEO2 + + ( fijar la Pantalla/y a la mitad de la pantalla, menos 4 ) + .Pantalla/alto DEI2 HALF2 #0004 SUB2 .Pantalla/y DEO2 + + ( fijar la dirección del sprite ) + ;cuadrado .Pantalla/direc DEO2 + + ( establecer vector de pantalla ) + ;en-cuadro .Pantalla/vector DEO2 +BRK + +@en-cuadro ( -> ) + ( 1: borrar sprite ) + ( borrar sprite del primer plano ) + color-borrar .Pantalla/sprite DEO + + ( 2: cambiar posición ) + ( incrementar sprite/pos-x ) + .sprite/pos-x LDZ2 INC2 .sprite/pos-x STZ2 + + ( 3 : dibujar sprite ) + ( carga la coordenada x de la página cero y la envía a la pantalla ) + .sprite/pos-x LDZ2 .Pantalla/x DEO2 + + ( dibujar sprite en el primer plano con color 2 y transparencia ) + color-2 .Pantalla/sprite DEO +BRK + +( datos del sprite ) +@cuadrado ff81 8181 8181 81ff +``` + +nítido, ¿no? :) + +como esto es sólo un ejemplo para ilustrar un punto, hay algunas cosas que podrían ser optimizadas para hacer nuestro programa más pequeño, y hay algunas cosas que podrían ser útiles pero fueron omitidas. por ejemplo, no hay un valor inicial para la coordenada x, o la coordenada y no se utiliza. + +### posibilidades adicionales + +con respecto a la optimización, y como un ejemplo, la sección 2 y la primera parte de la sección 3 de en-cuadro podrían haber sido escritas de la siguiente manera: + +``` + ( 2: cambio de posición ) + ( incrementar sprite/pos-x ) + .sprite/pos-x LDZ2 INC2 + DUP2 ( duplicar resultado ) + .sprite/pos-x STZ2 ( almacenar la primera copia del resultado ) + + ( 3 : dibujar sprite ) + ( usar la coordenada x de la pila y enviarla a la pantalla ) + .Pantalla/x DEO2 +``` + +como siempre, depende de nosotros cómo queremos navegar entre un código más corto y la legibilidad :) + +aquí hay algunas preguntas para que reflexiones y pruebes: + +* ¿cómo harías que el sprite se moviera más rápido? +* ¿y cómo harías que se moviera más lento? + +## cambio de posición interactivo + +cuando usamos el vector controlador, estamos actuando en base a un cambio en el/los botón/es o tecla/s que fueron presionados o liberados. esto puede ser muy útil para algunas aplicaciones. + +pero, ¿cómo podemos tratar de hacer una acción continua cuando una tecla se mantiene presionada? + +en algunos sistemas operativos, si mantenemos una tecla pulsada, ésta dispara el vector controlador varias veces, ¡pero no necesariamente al mismo ritmo que el vector pantalla! + +¡esta repetición puede no permitir un movimiento suave como el que podemos conseguir si comprobamos el estado del controlador dentro de la subrutina en-cuadro! + +### cuadrado horizontalmente interactivo + +el siguiente programa nos permite controlar la posición horizontal de nuestro cuadrado mediante las teclas de dirección. + +=> ./img/screencap_uxn-moving-square.gif animado que muestra un cuadrado moviéndose horizontalmente en la pantalla, aparentemente controlado por un humano. + +¡nótese las similitudes entre el programa anterior, y lo que cubrimos en el tutorial de uxn del día 3! +=> TUTORIAL UXN DÍA 3 + +``` +( hola-sprite-enmovimiento.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 ] + +( macros/constantes ) +%HALF2 { #01 SFT2 } ( desplazar un bit a la derecha ) ( corto -- corto/2 ) +%color-borrar { #40 } ( borrar 1bpp sprite del primer plano ) +%color-2 { #4a } ( dibujar sprite de 1bpp con color 2 y transparencia ) + +( página cero ) +|0000 +@sprite [ &pos-x $2 &pos-y $2 ] + +( init ) +|0100 + ( establecer colores del sistema ) + #2ce9 .Sistema/r DEO2 + #01c0 .Sistema/g DEO2 + #2ce5 .Sistema/b DEO2 + + ( fijar la Pantalla/y a la mitad de la pantalla, menos 4 ) + .Pantalla/alto DEI2 HALF2 #0004 SUB2 .Pantalla/y DEO2 + + ( fijar la dirección del sprite ) + ;cuadrado .Pantalla/direc DEO2 + + ( establecer vector de pantalla ) + ;en-cuadro .Pantalla/vector DEO2 +BRK + +@en-cuadro ( -> ) + ( 1: borrar sprite ) + ( borrar sprite del primer plano) + color-borrar .Pantalla/sprite DEO + + ( 2: cambiar de posición con las flechas ) + &verificar-flechas + .Controlador/boton DEI + #40 AND ( aislar el bit 6, correspondiente a la izquierda ) + ,&izquierda JCN ( saltar si no es 0 ) + + .Controlador/boton DEI + #80 AND ( aislar el bit 7, correspondiente a la derecha ) + ,&derecha JCN ( saltar si no es 0 ) + + ( si no se ha pulsado ninguna de esas teclas, dibujar sin cambios ) + ,&dibujar JMP + + &izquierda + ( disminuir sprite/pos-x ) + .sprite/pos-x LDZ2 #0001 SUB2 .sprite/pos-x STZ2 + ,&dibujar JMP + + &derecha + ( incrementar sprite/pos-x ) + .sprite/pos-x LDZ2 INC2 .sprite/pos-x STZ2 + + ( 3 : dibujar sprite ) + &dibujar + ( carga la coordenada x de la página cero y la envía a la pantalla ) + .sprite/pos-x LDZ2 .Pantalla/x DEO2 + + ( dibujar sprite en primero plano con el color 2 y transparencia ) + color-2 .Pantalla/sprite DEO +BRK + +( datos del sprite ) +@cuadrado ff81 8181 8181 81ff +``` + +¡te invito a que adaptes el código para que puedas controlar el sprite en las cuatro direcciones cardinales! + +## moviéndose dentro de los límites + +como habrás notado, estos dos programas anteriores permiten que nuestro sprite se salga de la pantalla. + +si quisiéramos evitar eso, una forma de hacerlo sería añadiendo (más) condicionales. + +### límites condicionales + +por ejemplo, en lugar de tener un incremento incondicional en la coordenada x: + +``` +( incremento sprite/pos-x ) +.sprite/pos-x LDZ2 INC2 .sprite/pos-x STZ2 +``` + +podríamos comprobar primero si ha alcanzado una cantidad específica, como el ancho de la pantalla, y no incrementar si es el caso: + +``` +.sprite/pos-x LDZ2 ( carga x ) +.Pantalla/ancho DEI2 #0008 SUB2 ( obtener el ancho de la pantalla menos 8 ) +EQU2 ( ¿es x igual a la anchura de la pantalla - 8? ) +&continuar JCN + + &incremento + ( incrementar sprite/pos-x ) + .sprite/pos-x LDZ2 INC2 .sprite/pos-x STZ2 + + &continuar +``` + +### módulo + +otra posibilidad podría ser aplicar una operación de módulo a nuestras coordenadas cambiadas para que siempre se mantengan dentro de los límites, volviendo a la izquierda cuando se cruce con la derecha, y viceversa. + +un posible conjunto de macros de módulo podría ser: + +``` +%MOD { DUP2 DIV MUL SUB } ( a b -- a%b ) +%MOD2 { OVR2 OVR2 DIV2 MUL2 SUB2 } ( a b -- a%b ) +``` + +(hay un conjunto más optimizado pero lo discutiremos más adelante :) + +podemos aplicar esas macros después de incrementar o decrementar. por ejemplo: + +``` +( incrementar sprite/pos-x ) +.sprite/pos-x LDZ2 INC2 +.Pantalla/ancho DEI2 MOD2 ( aplicar modulo de ancho de pantalla ) +.sprite/pos-x STZ2 ( almacenar el resultado ) +``` + +# animación de sprite con fotogramas + +otra estrategia de animación consistiría en cambiar el sprite que se dibuja en una posición determinada. + +podrías tener una secuencia de sprites/marcos y animarlos ejecutándolos en secuencia + +## los fotogramas + +a efectos prácticos te recomendaría tener un número de fotogramas correspondiente a una potencia de dos, como 2, 4, 8, 16, 32, etc. + +por ejemplo, lo siguiente es una secuencia de ocho sprites de 1bpp que corresponden a una línea diagonal que se mueve desde abajo a la derecha hasta arriba a la izquierda: + +``` +@animación + &fotograma0 00 00 00 00 01 03 + &fotograma1 00 00 00 01 03 06 0c + &fotograma2 00 00 01 03 06 0c 18 30 + &fotograma3 01 03 06 0c 18 30 60 c0 + &fotograma4 03 06 0c 18 30 60 c0 80 + &fotograma5 0c 18 30 60 c0 80 00 00 + &fotograma6 30 60 c0 80 00 00 00 + &fotograma7 c0 80 00 00 00 00 00 +``` + +nótese que cada fotograma consta de 8 bytes. eso implica que hay un desplazamiento de 8 bytes entre las direcciones correspondientes a cada subetiqueta. + +por ejemplo, la dirección de &fotograma1 sería 8 bytes más que la dirección de &fotograma0. + +los fotogramas que utilizas también podrían estar compuestos por sprites de 2bpp. en ese caso, el desplazamiento entre fotogramas sería de 16 en decimal (10 en hexadecimal) bytes. + +## conteo de fotogramas + +para tener una animación compuesta por esos fotogramas necesitamos cambiar la dirección de Pantalla/direc a intervalos específicos para que apunte a un sprite diferente cada vez. + +¿cómo podemos saber la dirección del sprite que debemos utilizar en cada fotograma? + +una forma de conseguirlo es teniendo una "variable global" en la página cero que cuente los fotogramas del programa. además, tendríamos que tener ese conteo acotado en un rango correspondiente a la cantidad de fotogramas de nuestra animación. + +ya sabemos cómo hacer la primera parte, y más o menos sabemos cómo hacer la segunda + +### cargar, incrementar y almacenar la cuenta de fotogramas + +en la página cero declaramos la etiqueta para nuestro cuentafotogramas. utilizaremos la abreviación "cuentaftg" para ayudar a la legibilidad: + +``` +( página cero ) +|0000 +@cuentaftg $1 +``` +y en la subrutina en-cuadro lo incrementamos: + +``` +( incrementar cuenta de fotograma ) +.cuentaftg LDZ INC .cuentaftg STZ +``` + +ten en cuenta que estamos usando un solo byte para contar, por lo que pasará de 0 a 255 en poco más de 4 segundos, y luego se reiniciará cuando sobrepase su cuenta. + +para algunas aplicaciones podría ser mejor tener un cuentafotograma en un corto, que contaría de 0 a 65535 y se sobrepasaría en un poco más de 18 minutos. + +### módulo rápido + +para que ese recuento de fotogramas se limite a un rango correspondiente a nuestro número de fotogramas, podemos utilizar una operación de módulo. + +cuando tenemos un número de fotogramas que corresponde a una potencia de dos, como se recomienda más arriba, podemos utilizar una "máscara AND" para realizar esta operación de módulo más rápidamente que si utilizáramos las macros MOD sugeridas anteriormente. + +por ejemplo, si tenemos 8 fotogramas numerados del 0 al 7, podemos observar que esos números sólo requieren tres bits para ser representados. + +para construir nuestra máscara AND, ponemos como 1 esos tres bits, y 0 los demás: + +``` +0000 0111: 07 +``` + +esta máscara AND "dejará pasar" los tres bits menos significativos de otro byte, y desactivará los demás. + +en uxntal este proceso se vería de la siguiente manera: + +``` +.cuentaftg LDZ ( cargar cuentafotograma ) +#07 AND ( aplicar máscara AND, correspondiente al módulo 8 ) +``` + +el resultado de la operación será un conteo que va repetidamente de 0 a 7. + +podríamos definir esta operación de módulo rápido como una macro para hacer el código más legible: + +``` +%8MOD { #07 AND } ( byte -- byte%8 ) +``` + +si esto no te ha quedado muy claro, te recomiendo que vuelvas a mirar el tutorial de uxn del día 3, en particular la discusión de las operaciones lógicas. +=> TUTORIAL UXN DÍA 3 + +## aritmética de punteros + +¿cómo podemos usar esa cuenta para seleccionar el sprite para el cuadro de animación que queremos mostrar? + +podríamos usar varios saltos condicionales, o podríamos usar una forma más divertida que se puede llamar aritmética de punteros :) + +observa que la subetiqueta para el primer fotograma (fotograma0) de nuestra animación tiene la misma dirección que la etiqueta para toda la animación. y, como ya hemos mencionado, el siguiente fotograma (fotograma1) comienza 8 bytes después. + +la subetiqueta de cada fotograma siguiente está 8 bytes después de la anterior. + +o, otra forma de verlo: + +* el fotograma 0 es 0 bytes después de la etiqueta de animación +* El fotograma 1 es 8 bytes después de la etiqueta de animación +* El fotograma 2 es de 16 bytes después de la etiqueta de animación +* El fotograma 3 es de 24 bytes después de la etiqueta de animación +* y así sucesivamente + +generalizando, ¡el fotogramaN esta (N veces 8) bytes después de la etiqueta de animación! + +esto significa que si obtenemos la dirección absoluta de la etiqueta de animación, y le añadimos (N veces 8) bytes, obtendremos la dirección absoluta del fotogramaN :) + +esta cantidad de bytes que separa cada subetiqueta se llama offset. + +### calculando el offset + +después de aplicar el módulo 8 a nuestro cuentafotogramas podemos multiplicarlo por 8 para obtener el offset respecto a la etiqueta de la animación: + +``` +.cuentaftg LDZ ( cargar cuentafotograma ) +8MOD ( aplicar el módulo 8 para obtener la secuencia entre 0 y 7 ) +#08 MUL ( multiplicar por 8 para obtener el desplazamiento ) +``` + +### de byte a corto + +nota que hasta ahora hemos estado trabajando con bytes, y todo ha ido bien. + +sin embargo, ¡las direcciones absolutas son cortos! + +esto significa que tenemos que convertir nuestro offset en un short para poder añadirlo a la dirección de los datos de la animación. + +una forma de hacerlo es con esta macro que añade un 00 antes del elemento superior de la pila: + +``` +%A-CORTO { #00 SWP } ( byte -- corto ) +``` + +nuestro código quedaría de la siguiente manera + +``` +.cuentaftg LDZ ( cargar cuentafotograma ) +8MOD ( aplicar el módulo 8 para obtener la secuencia entre 0 y 7 ) +#08 MUL ( multiplicar por 8 para obtener el desplazamiento ) +A-CORTO ( convertir a corto ) +``` + +otra forma, menos clara pero bastante divertida (y algo más corta en memoria de programa), consistiría en pulsar el 00 antes de que ocurra cualquier otra cosa: + +``` +#00 ( empujar el byte alto del offset ) +.cuentaftg LDZ ( cargar cuentafotograma ) +8MOD ( aplicar el módulo 8 para obtener la secuencia entre 0 y 7 ) +#08 MUL ( multiplicar por 8 para obtener el offset ) +``` + +### añadiendo el offset + +añadir este offset a la dirección de nuestra animación es comparativamente sencillo: + +``` +.cuentaftg LDZ ( cargar cuentafotogramas ) +8MOD ( aplicar el módulo 8 para obtener la secuencia entre 0 y 7 ) +#08 MUL ( multiplicar por 8 para obtener el desplazamiento ) +A-CORTO ( convertir a corto ) +;animacion ( obtener la dirección de la animación ) +ADD2 ( añadir el desplazamiento a la dirección ) +``` + +y entonces podríamos enviar eso al puerto Pantalla/direc: + +``` +.Pantalla/direc DEO2 ( establecer la dirección calculada ) +``` + + +## el programa completo + +el programa que hace todo esto tendría el siguiente aspecto. + +nota que utiliza una secuencia similar a la de los programas anteriores: + +* incrementar el cuentafotograma +* borrar el sprite +* calcular la dirección del sprite +* dibujar sprite + +la sección "borrar el sprite" no es realmente necesaria en este caso debido a los colores que se utilizan, pero lo sería cuando se utilizan colores con transparencia en ellos :) + +=> ./img/screencap_uxn-animation.gif animación de una franja diagonal dentro de un cuadrado pixelado. la diagonal se mueve desde abajo a la derecha hasta arriba a la izquierda + +``` +( hola-animacion.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 ] + +( macros/constantes ) +%HALF2 { #01 SFT2 } ( desplazar un bit a la derecha ) ( corto -- corto/2 ) +%8MOD { #07 AND } ( byte -- byte%8 ) +%A-CORTO { #00 SWP } ( byte -- corto ) + +%color-borrar { #40 } ( borrar sprite de 1bpp del primer plano ) +%color-2-3 { #4e } ( dibujar el sprite de 1bpp con el color 2 y 3 ) + +( página cero ) +|0000 +@cuentaftg $1 + +( init ) +|0100 + ( set system colors ) + #2ce9 .Sistema/r DEO2 + #01c0 .Sistema/g DEO2 + #2ce5 .Sistema/b DEO2 + + ( fijar Pantalla/x y y a la mitad de la pantalla menos 4 ) + .Pantalla/ancho DEI2 HALF2 #0004 SUB2 .Pantalla/x DEO2 + .Pantalla/alto DEI2 HALF2 #0004 SUB2 .Pantalla/y DEO2 + + ( establecer la dirección del sprite ) + ;animación .Pantalla/direc DEO2 + + ( establecer vector de pantalla ) + ;en-cuadro .Pantalla/vector DEO2 + +BRK + +@en-cuadro ( -> ) + ( 0: incrementar el número de fotogramas ) + .cuentaftg LDZ INC .cuentaftg STZ + + ( 1: borrar sprite ) + ( borrar sprite del primer plano ) + color-clear .Pantalla/sprite DEO + + ( 2: actualizar la dirección del sprite ) + .cuentaftg LDZ ( cargar cuentafotograma ) + 8MOD ( aplicar el módulo 8 para obtener la secuencia entre 0 y 7 ) + #08 MUL ( multiplicar por 8 para obtener el desplazamiento ) + TO-SHORT ( convertir a corto ) + ;animation ( obtener la dirección de la animación ) + ADD2 ( añadir el offset a la dirección ) + .Pantalla/direc DEO2 ( establecer la dirección calculada ) + + ( dibujar sprite en el primer plano con el color 2 y 3 ) + color-2-3 .Pantalla/sprite DEO +BRK + +( datos del sprite ) +@animacion + &fotograma0 00 00 00 00 01 03 + &fotograma1 00 00 00 01 03 06 0c + &fotograma2 00 00 01 03 06 0c 18 30 + &fotograma3 01 03 06 0c 18 30 60 c0 + &fotograma4 03 06 0c 18 30 60 c0 80 + &fotograma5 0c 18 30 60 c0 80 00 00 + &fotograma6 30 60 c0 80 00 00 00 + &fotograma7 c0 80 00 00 00 00 00 +``` + +no era tan complicado, ¿verdad? :) este ejemplo incluye muchos conceptos que merecen ser estudiados, ¡así que te invito a leerlo con atención! + +para algunas posibilidades de diversión, te invito a dibujar el tile varias veces en diferentes lugares y posiblemente con diferentes modos de rotación! eso puede generar animaciones más interesantes! + +o, mejor aún, ¡diseña y utiliza tus propios sprites! + +## ¡más despacio! + +hasta ahora, todo lo que hemos estado haciendo ha sucedido a 60 cuadros por segundo, ¡eso puede ser demasiado rápido para algunas aplicaciones! + +afortunadamente, podemos usar algo de aritmética simple con nuestro cuentafotograma para desacelerar sus efectos. + +por ejemplo, si queremos actualizar nuestros fotogramas a la mitad de esa velocidad (30 fotogramas por segundo), podemos dividir entre dos el valor del cuentafotograma antes de aplicar el módulo. + +como recordarás, esta división se puede hacer con SFT en el caso de potencias de dos, o con DIV para cualquier otro caso. + +``` +%MITAD { #01 SFT } ( byte -- byte/2 ) +%CUARTO { #02 SFT } ( byte -- byte/4 ) +%OCTAVO { #03 SFT } ( byte -- byte/8 ) +``` + +podemos utilizar estas macros para dividir la frecuencia en nuestro código: + +``` + ( 2: actualizar la dirección del sprite ) + .cuentaftg LDZ ( cargar cuentafotograma ) + CUARTO ( dividir entre 4 la frecuencia ) + 8MOD ( aplicar el módulo 8 para obtener la secuencia entre 0 y 7 ) + #08 MUL ( multiplicar por 8 para obtener el desplazamiento ) + A-CORTO ( convertir a corto ) + ;animacion ( obtener la dirección de la animación ) + ADD2 ( añadir el offset a la dirección ) + .Pantalla/direc DEO2 ( establecer la dirección calculada ) +``` + + +=> ./img/screencap_uxn-animation-quarterspeed.gif animación de una franja diagonal dentro de un cuadrado pixelado. la diagonal se mueve desde abajo a la derecha hasta arriba a la izquierda. se mueve más lentamente que la anterior. + +ah, ¡mucho mejor! + +## potencias de dos no + +ten en cuenta que si quieres dividir la frecuencia a números que no son potencias de 2, podrías empezar a ver algunos glitches aproximadamente cada 4 segundos: esto se debe a que el cuentafotograma se sobrepasa y no da una buena secuencia de resultados para esos divisores. + +esto también puede ocurrir si tienes una animación que consta de un número de fotogramas que no es una potencia de 2, y utilizas una operación MOD normal para calcular el desplazamiento al fotograma correspondiente. + +la solución más sencilla para estos problemas sería utilizar un número de fotogramas de tamaño reducido que sólo causara esos fallos de sobreflujo aproximadamente cada 18 minutos. + +tendrías que adaptar el programa para que funcione con ese tamaño de cuentafotograma - ¡siento y pienso que es un buen ejercicio! + +# instrucciones del día 4 + +¡estas son todas las instrucciones de uxntal que hemos discutido hoy! + +* LDA: carga y empuja hacia abajo en la pila el valor en la dirección absoluta dada ( dirección -- valor ) +* STA: almacena en la dirección absoluta el valor dado ( dirección valor -- ) +* LDZ: carga y empuja hacia abajo en la pila el valor en la dirección de página cero dada ( dirección -- valor ) +* STZ: almacena en la dirección de página cero el valor dado ( dirección del valor -- ) +* LDR: carga y empuja hacia abajo en la pila el valor en la dirección relativa dada ( dirección -- valor ) +* STR: almacena en la dirección relativa dada el valor dado ( dirección del valor -- ) + +las direcciones de LDA y STA son siempre cortos, mientras que las direcciones de las demás instrucciones son siempre un byte. + +en modo corto, estas instrucciones cargan o almacenan cortos desde o hacia la memoria. + +# día 5 + +en el tutorial de uxn día 5 introducimos el dispositivo de ratón varvara para explorar más interacciones posibles, y cubrimos los elementos restantes de uxntal y uxn: la pila de retorno, el modo de retorno y el modo de mantenimiento. +=> TUTORIAL UXN DÍA 5 + +¡también discutimos posibles estructuras para crear bucles y programas más complejos utilizando estos recursos! + +¡primero te invito a tomar un descanso! + +después, ¡sigue explorando y comparte tus descubrimientos! + +# apoyo + +si te ha gustado este tutorial y te ha resultado útil, considera compartirlo y darle tu apoyo :) + +=> ENLACE DE APOYO \ No newline at end of file