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 manejos complejos en la pila :)
uxn saltará a la ubicación de la etiqueta a una frecuencia de 60 veces por segundo: podemos utilizar la subrutina bajo en-cuadro para cambiar el contenido de la pantalla, generando animación, o también podemos utilizarla para otros propósitos relacionados con la temporización.
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:
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?
recuerda que $2 crea un pad relativo de dos bytes: esto hace que píxel-y sea una etiqueta para una dirección en memoria dos bytes después de píxel-x. y cualquier código posterior ocurrirá dos bytes después de píxel-y.
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:
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ás, 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.
para referirnos a ellas, utilizaríamos la runa punto (.) para las direcciones literales de página cero, en lugar de la runa punto y coma (;) para las direcciones literales absolutas.
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.
además, observe que en el caso de .píxel 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 DEI/DEO.
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 :)
sin embargo, como estas direcciones se dan como desfases ("offsets") relativos y pueden considerarse positivas y negativas, solo se pueden alcanzar si están dentro de los 256 bytes que rodean a la instrucción que las carga o almacena.
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 )
nótese el uso de la runa coma (,) para indicar que es una dirección relativa; uxnasm calcula el desfase requerido asumiendo que será utilizado en la siguiente instrucción.
en este caso realmente no podemos duplicar ese desfase 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.
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/píxel, 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.
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!
como esto es solo 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.
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?
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 soltados. esto puede ser muy útil para algunas aplicaciones.
en algunos sistemas operativos, si mantenemos una tecla pulsada, esta 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 animación que muestra un cuadrado moviéndose horizontalmente en la pantalla, aparentemente controlado por un humano.
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.
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:
los fotogramas que utilizas también podrían estar compuestos por sprites de 2bpp. en ese caso, el desfase entre fotogramas sería de 16 en decimal (10 en hexadecimal) bytes.
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.
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.
si esto no te ha quedado muy claro, te recomiendo que vuelvas a mirar el {tutorial de uxn día 3}, en particular la discusión de las operaciones lógicas.
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 mencionamos, el siguiente fotograma (fotograma1) comienza 8 bytes después.
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 :)
otra forma, menos clara pero bastante divertida (y algo más corta en memoria de programa), consistiría en empujar el 00 antes de que ocurra cualquier otra cosa:
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
para algunas posibilidad divertidas, ¡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!
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 )
.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.
ten en cuenta que si quieres dividir la frecuencia a números que no son potencias de 2, podrías empezar a ver algunas fallas ("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 desfase al fotograma correspondiente.
la solución más sencilla para estos problemas sería utilizar un número de fotogramas de pequeño tamaño que solo causara esos fallos de sobreflujo aproximadamente cada 18 minutos.
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 mantener.