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, y/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 pixel-x. y cualquier código posterior ocurrirá dos bytes después de píxel-y.
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á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.
### 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:
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 .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 DEI/DEO.
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:
sin embargo, como estas direcciones se dan como desfases ("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.
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 )
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.
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!
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!
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.
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.
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 animación que muestra un cuadrado moviéndose horizontalmente en la pantalla, aparentemente controlado por un humano.
#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.
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.
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:
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.
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 :)
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 sólo 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.