Compare commits

...

32 Commits

Author SHA1 Message Date
jota 804a750d8b WIP: Traducción Día 6 y otros cambios breves 2022-01-11 16:32:15 -03:00
sejo 63740eac22 itch.io widget 2022-01-10 17:52:45 -06:00
sejo d5a0401b00 Merge pull request 'Traducción Tutorial de Uxn Día 5 + correcciones breves Day 5 y Día 2' (#4) from jota/compudanzas:traducción_tutorial_de_uxn_día_5 into main
Reviewed-on: sejo/compudanzas#4
2022-01-10 21:50:01 +00:00
jota 013f06e5f4 Añadí el documento del Día 6 2022-01-10 18:31:57 -03:00
jota b535a227b8 Añadí el documento del Día 6 2022-01-10 18:30:16 -03:00
jota b1879381b1 Añadí el documento del Día 6 2022-01-10 17:32:08 -03:00
jota c6052a9e05 Traducción del Tutorial de Uxn Día 5, revisión del Día 5 (inglés) y correción breve del Día 2 (español) 2022-01-10 17:00:16 -03:00
jota 97d4c7b9f6 Traducción del Tutorial de Uxn Día 5, revisión del Día 5 (inglés) y correción breve del Día 2 (español) 2022-01-10 16:59:20 -03:00
sejo 63277cfb66 Merge pull request 'Revisión del día 1 (inglés y español) y el índice del tutorial (español)' (#3) from jota/compudanzas:tutorial_rev_dia_1_e_indice into main
Reviewed-on: sejo/compudanzas#3
2022-01-10 19:45:42 +00:00
jota 9496df9334 Revisión del día 1 (inglés y español) y el índice del tutorial (español) 2022-01-10 11:20:31 -03:00
sejo f4c517a192 publications page 2022-01-09 18:23:33 -06:00
sejo a91497f0b7 intro to uxn programming book 2022-01-09 15:25:18 -06:00
sejo 3c4dd48c55 correcciones para compatibilidad 2022-01-08 19:43:02 -06:00
sejo 88ecd85fe1 replaced rfc3339 option 2022-01-08 12:58:19 -06:00
sejo d28a7889b4 posix compatible awk 2022-01-08 11:59:43 -06:00
sejo c41e65b551 cleaned up root dir 2022-01-07 20:38:44 -06:00
sejo 77ecb71c8f virtual pet jam in roadmap 2022-01-07 20:33:12 -06:00
sejo c66b28f92a revision day 7 2022-01-07 20:17:13 -06:00
sejo 073dd3057c revision day 6 2022-01-07 19:22:09 -06:00
sejo 06cb8de530 archivo de día 5 provisional 2022-01-07 17:44:16 -06:00
sejo 3c42655a6f Merge branch 'main' of tildegit.org:sejo/compudanzas 2022-01-07 17:41:53 -06:00
sejo 98fd37df09 Merge pull request 'Correciones de los días 2, 3 y 4' (#2) from jota/compudanzas:tutorial_de_uxn_días_2_a_4 into main
Reviewed-on: sejo/compudanzas#2
2022-01-07 23:41:09 +00:00
jota 8e5177007a Me olvide unas corcheas malpuestas 2022-01-07 20:38:36 -03:00
sejo 59fa19dce0 removed mouse mention 2022-01-07 17:34:44 -06:00
sejo 73c3772200 add .gitignore to tracked files 2022-01-07 17:33:42 -06:00
jota 6ede6de779 Correciones de los días 2, 3 y 4 2022-01-07 20:29:40 -03:00
sejo 937e1cba8c revision uxn tutorial day 5 2022-01-07 16:45:18 -06:00
sejo 0086bd73a5 Merge branch 'main' of tildegit.org:sejo/compudanzas 2022-01-07 15:16:11 -06:00
sejo 3aa890efe4 corrected mouse device specs on day 5 2022-01-07 15:15:29 -06:00
sejo abaec8f8ce Merge pull request 'Añadidos los días 2, 3 y 4 al tutorial en español de Uxn' (#1) from jota/compudanzas:tutorial_de_uxn_días_2_a_4 into main
Reviewed-on: sejo/compudanzas#1
2022-01-07 21:13:54 +00:00
sejo 118503c983 make no es necesario 2022-01-07 15:01:17 -06:00
jota a0e7168657 Añadidos los días 2, 3 y 4 al tutorial en español de Uxn 2022-01-07 17:45:19 -03:00
33 changed files with 5245 additions and 210 deletions

10
.gitignore vendored Normal file
View File

@ -0,0 +1,10 @@
*swp
Makefile
web/*
gem/*
src/pages.gmo
tmp/*
generador
gemtest
webtest
src/log.gmo

View File

@ -55,7 +55,7 @@ function appendNav( t ){
}
function wikiLink( t ){
i = match( t, /{.+}/)
i = match( t, /\{.+\}/)
if ( i ){
ifinal = index(t, "}") # índice del } final
@ -109,8 +109,8 @@ NR == 1{
/^\+ /{ # include literal
sub(/^+ /,"",$0) # elimina el +
sub(/^\+[[:blank:]]+/,""){ # include literal
# sub(/^+ /,"",$0) # elimina el +
appendContenido( $0 )
next
}
@ -183,8 +183,8 @@ NR == 1{
sub(".gmi$",".html",$1)
# quita { }
sub("{","",texto)
sub("}","",texto)
sub(/\{/,"",texto)
sub(/\}/,"",texto)
# crea link <a>
appendContenido("<p><a href='"$1"' class=arrow>"texto"</a></p>")
@ -203,7 +203,7 @@ NR == 1{
next
}
/^* /{ # lista
/^\* /{ # lista
if(!modo_pre){
if(!modo_lista){ # inicia la lista
if(modo_parrafo){
@ -213,8 +213,8 @@ NR == 1{
modo_lista = 1
appendContenido( "<ul>" )
}
sub("*[[:blank:]]+","<li>",$0)
sub("$","</li>",$0)
sub(/\*[[:blank:]]+/,"<li>",$0)
sub(/$/,"</li>",$0)
appendContenido( wikiLink($0) )
}
else

View File

@ -5,12 +5,12 @@ mkdir -p gem
mkdir -p tmp
fecha=$(date --rfc-3339=date)
fecha=$(date +%F)
echo "empezando conversión, estamos en ${fecha}"
echo "revisando y copiando imágenes..."
for f in $(find src/ -regextype awk -regex ".*(jpg|png|gif|svg)")
for f in $(find src/ -regex ".*\(jpg\|png\|gif\|svg\)")
do
path="web/${f#src/}" # quita el prefijo de src/ y agrega web/
gempath="gem/${f#src/}" # quita el prefijo de src/ y agrega gem/
@ -59,7 +59,7 @@ do
htmlpath="web/${path%gmo}html" # agrega "web/" y cambia el sufijo
gempath="gem/${path%gmo}gmi" # agrega "gem/"
fecha=$(date -r src/$path --rfc-3339=date)
fecha=$(date -r src/$path +%F)
# haz la conversión
awk -v fecha=$fecha -f gemtext2html.awk $f > $htmlpath

View File

@ -23,7 +23,7 @@ BEGIN{
FS = "\t"
"date --rfc-3339=date"| getline fecha
"date +%F"| getline fecha
system("cp " loggmoheader " " loggmopath)
system("cp " gematomheader " " atomgempath)
@ -60,9 +60,9 @@ BEGIN{
print "<title>" $2 "</title>" >> atomgempath
print "<updated>" $1 "</updated>" >> atomgempath
if ( match($3, /{.+}/) ){ # si hay wikilink
gsub("{","",$3)
gsub("}","",$3)
if ( match($3, /\{.+\}/) ){ # si hay wikilink
gsub(/\{/,"",$3)
gsub(/\}/,"",$3)
filename = $3
gsub(" ","_",filename)

View File

@ -3,7 +3,7 @@ function nombre2Link( t, r ){ # convierte un nombre con espacios, a uno con r (e
return t
}
/^+ /{ # literal html
/^\+ /{ # literal html
next # salta la línea
}
@ -21,7 +21,7 @@ function nombre2Link( t, r ){ # convierte un nombre con espacios, a uno con r (e
# si tienen sintaxis de wikilink, y no son líneas de enlace, agrega el link
# (el % es por las líneas de uxn jaja)
/^[^=%]*{.+}/{
i = match( $0, /{.+}/)
i = match( $0, /\{.+\}/)
ifinal = index($0, "}") # índice del } final
link = substr($0, i, ifinal-i+1) # {link}

View File

@ -42,7 +42,6 @@ for filename in os.listdir():
file.close()
os.chdir('../tmp/')
# remove incoming links for:
# incoming.pop('{pages}')
incoming.pop('{home}')

View File

@ -12,7 +12,7 @@ the static site generator for the compudanzas.net {wiki} is mostly written in aw
g2e is an opinionated gempub to epub converter written in awk:
=> https://tildegit.org/sejo/g2e
=> https://tildegit.org/sejo/g2e g2e
my solutions for {advent of code 2021} have been written in awk.

View File

@ -7,11 +7,12 @@ explorando computación a escala humana: ¿qué pasa cuando las computadoras son
# proyectos
=> ./introduction_to_uxn_programming_book.gmi {introduction to uxn programming book}
=> ./qiudanz_technique.gmi {qiudanz technique}
=> ./talks_and_workshops.gmi {talks and workshops}
=> ./uxn_tutorial.gmi {uxn tutorial}
=> ./las_danzas.gmi {las danzas}
=> ./coloring_computers.gmi {coloring computers}
=> ./talks_and_workshops.gmi {talks and workshops}
=> ./publications.gmi {publications}
# acerca

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

View File

@ -2,7 +2,9 @@
online workshop to learn the basics of {uxn} programming.
if you prefer text or prefer to dig deeper, you can check out our {uxn tutorial}!
in order to dig deeper, you can get and read our {introduction to uxn programming book}!
alternatively, you can check out our {uxn tutorial} online.
# at babycastles

View File

@ -0,0 +1,24 @@
# introduction to uxn programming book
introduction to uxn programming is a beginner's, slow-paced and comprehensive guide of the uxntal programming language and the varvara computer.
=> ./img/foto_intro-to-uxn-programming.png photo of an ebook reader showing an illustration and metadata of the book
=> ./img/foto_intro-to-uxn-programming_2.png photo of an ebook reader showing the introduction of the book
originally available online as the {uxn tutorial}, this is a carefully revised version for you to have it offline and available anytime.
# download
=> https://compudanzas.itch.io/introduction-to-uxn-programming get introduction to uxn programming
+ <iframe src="https://itch.io/embed/1346609?linkback=true" width="552" height="167" frameborder="0"><a href="https://compudanzas.itch.io/introduction-to-uxn-programming">introduction to uxn programming by compudanzas</a></iframe>
# more info
written and illustrated by {sejo}, with a foreword by Devine Lu Linvega
=> https://wiki.xxiivv.com/site/devine_lu_linvega.html Devine Lu Linvega
the bundle contains an ebook in EPUB and GEMPUB formats crafted with our own {awk}-based tools, and an automatically converted ebook in MOBI format.
by buying the book, you help to {support} compudanzas and our projects!

View File

@ -1,3 +1,4 @@
2022-01-09T12:00:00-06:00 published our ebook: introduction to uxn programming! {introduction to uxn programming book}
2022-01-06T16:00:00-06:00 notas para colaborar con traducciones {traducciones}
2021-12-29T15:00:00-06:00 reviewed and published our highlights for the year {highlights of 12021}
2021-12-17T14:00:00-06:00 built the tomatimer, a pomodoro timer on an attiny85 programmed with avr-asm {tomatimer}

View File

@ -2,8 +2,8 @@
what we are up to approximately now.
writing and reviewing the {highlights of 12021}.
publishing our {introduction to uxn programming book}.
preparing an epub/gempub version of the {uxn tutorial}
planning some projects related to {beans computing}.
preparing for {running} a marathon in some weeks.

View File

@ -8,3 +8,5 @@ non-executable poetic texts around compudanzas.
=> ./the_rite_of_computing.gmi {the rite of computing}
see also the {proposals} for more texts surrounding the project.
we also have a couple of {publications}

22
src/publications.gmo Normal file
View File

@ -0,0 +1,22 @@
# publications
works on physical or digital paper.
see also: {poetry}
# introduction to uxn programming
{introduction to uxn programming book}: a beginner's, slow-paced and comprehensive guide of the uxntal programming language and the varvara computer.
=> ./img/foto_intro-to-uxn-programming.png photo of an ebook reader showing an illustration and metadata of the book
=> ./img/foto_intro-to-uxn-programming_2.png photo of an ebook reader showing the introduction of the book
=> https://compudanzas.itch.io/introduction-to-uxn-programming get introduction to uxn programming
# coloring computers
{coloring computers}: non-electronic computers that work when you color them according to a simple set of rules.
=> ./img/foto_coloring-computers_cover-lee.png photo of the cover of the zine, colored
=> ./img/foto_coloring-computers_7seg-lee.png photo of a pair of colored pages of the zine, with a 7 segment display showing the digits 2 and 3
=> ./img/foto_coloring-computers_pcd2019.png photo of a pair of colored pages of the zine, showing a digital circuit answering if two colors are the same

View File

@ -50,3 +50,6 @@ complete, organize and clean the following:
* develop a {computadora de papel} simulator?
* paper cards-based uxn implementation?
* uxn {verilog} implementation?
participate in the virtual pet jam with some ideas i have about tabletop {beans computing}
=> https://itch.io/jam/virtual-pet-jam virtual pet jam - itch.io

View File

@ -18,5 +18,7 @@ and/or you can support us via ko-fi!
=> https://ko-fi.com/compudanzas compudanzas on ko-fi
alternatively, you can get the {introduction to uxn programming book}!
thank you very much!
sejo

View File

@ -102,7 +102,7 @@ $ cd compudanzas/
### generando el sitio
ya en el repositorio puedes ejecutar el generador, que requiere de la existencia de make, python3 (y {awk}, aunque ese ya es parte de unix) en el sistema:
ya en el repositorio puedes ejecutar el generador, que requiere de la existencia de python3 (y {awk}, aunque ese ya es parte de unix) en el sistema:
```
$ ./generasitio.sh

View File

@ -9,7 +9,6 @@ el tutorial está dividido en 8 días (o secciones), ya que puede ser seguido ju
(al día de hoy, este es un trabajo en proceso)
# día 1
en esta primera sección del tutorial vamos a hablar de los aspectos básicos de la computadora uxn llamada varvara, su paradigma de programación, su arquitectura, y por qué podrías queres aprender a programar en ella.
@ -21,11 +20,11 @@ también vamos a saltar directo a nuestros primeros programas simples para demos
# día 2
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 cortos (2 bytes) además de los números de un sólo byte en uxntal.
{tutorial de uxn día 2}
# día 3
aquí introducimos el uso del dispositivo controlador en la computadora varvara: esto nos permite agregar interactividad a nuestros programas, y empezar a implementar control de flujo en uxntal.
@ -36,6 +35,22 @@ también hablamos de instrucciones lógicas y de manipulación de la pila en uxn
# día 4
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".
{tutorial de uxn día 4}
# día 5
aquí introducimos el dispositivo de ratón varvara para explorar más posibles interacciones, y cubrimos los elementos restantes de uxntal y uxn: la pila de retorno, el modo de retorno y el modo de retención.
también discutimos posibles estructuras para crear bucles y programas más complejos utilizando estos recursos.
{tutorial de uxn día 5}
# día 6
¡proximamente!
# índice tentativo
@ -129,6 +144,6 @@ modo nuevo: modo de retención
* comparte lo que has creado :)
# soporte
# apoyo
si este tutorial te ha sido de ayuda, considera compartirli y brindarle tu {apoyo} :)
si este tutorial te ha sido de ayuda, considera compartirlo y brindarle tu {apoyo} :)

View File

@ -20,7 +20,7 @@ uxn es el núcleo de la computadora virtual (¿por el momento?) varvara. es lo s
personalmente, veo en ella las siguientes virtudes:
* hecha para el largo plazo
* hecha para el largo plazo, conforme a la escala humana
* hecha para aplicaciones audiovisuales interactivas
* arquitectura y set de instrucciones simple (¡sólo 32 instrucciones!)
* primero-offline: funciona localmente y sólo necesitas unos pocos archivos de documentación para avanzar
@ -128,11 +128,11 @@ vamos a ir cubriendo lentamente estas instrucciones durante este tutorial.
la memoria en la computadora uxn consiste en cuatro espacios separados:
* memoria principal, con 65536 bytes
* memoria de entrada/salida, con 256 bytes dividida en 16 secciines (o dispositivos) de 16 bytes cada uno: 8 bytes para entradas y 8 bytes para salidas.
* memoria de entrada/salida, con 256 bytes dividida en 16 secciones (o dispositivos) de 16 bytes cada uno: 8 bytes para entradas y 8 bytes para salidas.
* pila de trabajo, con 256 bytes
* pila de retorno, con 256 bytes
cada byte en la memoria principal posee una dirección de 16 bits (2 bytes) de tamaño, mientras que cada byte en la memoria de entrada/salida posee una dirección de 8 bits (1 byte) de tamaño. ambas pueden ser accedidas de manera aleatoria.
cada byte en la memoria principal posee una dirección de 16 bits (2 bytes) de tamaño, mientras que cada byte en la memoria de entrada/salida posee una dirección de 8 bits (1 byte) de tamaño. ambos pueden ser accedidos aleatoriamente.
los primeros 256 bytes de la memoria principal constituyen una sección llamada página cero. esta sección puede ser referenciada por 8 bits (1 byte), y su propósito es almacenar datos durante el tiempo de ejecución de la máquina.
@ -154,10 +154,14 @@ la instrucción va a implicar normalmente un cambio en la(s) pila(s), y algunas
# instalación y toolchain
¿liste? obtengamos el ensamblador uxn (uxnasm) y emulador (uxnemu) de su repositorio git:
##instalación local
¿liste? para ejecutar varvara localmente y fuera de la red, tendríamos que obtener el ensamblador uxn (uxnasm) y el emulador (uxnemu) de su repositorio git:
=> https://git.sr.ht/~rabbits/uxn ~rabbits/uxn - sourcehut git
puedes construir estas herramientas desde el código fuente o descargar binarios precompilados para múltiples plataformas.
estas instrucciones son para sistemas basados en linux.
si necesitas una mano, encuéntranos en #uxn en irc.esper.net :)
@ -195,6 +199,7 @@ si todo fué bién, verás varios mensajes en la terminal y una pequeña ventana
* F1 itera entre diferentes niveles de zoom
* F2 muestra el debugger en pantalla
* F3 toma una captura de pantalla de la ventana
* F4 carga un boot.rom que permite navegar y abrir roms en el directorio actual
## usando el toolchain
@ -204,17 +209,23 @@ verás que luego de compilar uxn, cuentas con tres nuevos archivos ejecutables e
* uxnasm: el ensamblador
* uxncli: un emulador de consola no interactivo
en principio puedes hacer doble clic en uxnemu y que se ejecute. sin embargo, utilizaremos estos programas desde la línea de comandos.
puedes ajustar tu $PATH para tenerlos disponibles en todos lados.
la idea es que para correr un programa escrito en uxntal (el lenguaje ensamblador de uxn), primero tienes que ensamblarlo en una "rom", y luego puedes correr esta rom con el emulador.
por ejemplo, para correr {darena} que se encuentra en projects/examples/demos/ :
```
ensambla darena.tal en darena.rom
$ ./bin/uxnasm projects/examples/demos/darena.tal bin/darena.rom
ensambla darena.tal en darena.rom:
correr darena.rom
```
$ ./bin/uxnasm projects/examples/demos/darena.tal bin/darena.rom
```
corre darena.rom:
```
$ ./bin/uxnemu bin/darena.rom
```
@ -244,7 +255,7 @@ ahora que estamos en eso, hay una instrucción complementaria, SUB (resta) (opco
SUB ( a b -- a-b )
```
nota que el orden de los operandos es similar al de la división que discutimos arriba cuando hablamos de notación postfija.
nota que el orden de los operandos es similar al de la división que discutimos arriba cuando hablamos de notación postfija: es como si trasladáramos el operador de entre los operandos, hacia el final, después del segundo operando.
## un primer programa
@ -320,9 +331,9 @@ leyendo el programa de izquierda a derecha, podemos ver el siguiente comportamie
mirando en la tabla de dispositivos de la referencia varvara, podemos ver que el dispositivo con la dirección 1 en el nibble superior es la consola (entrada y salida estandard), y que la columna con la dirección 8 corresponde a "escritura".
=> https://wiki.xxiivv.com/site/uxnemu.html uxnemu
=> https://wiki.xxiivv.com/site/varvara.html varvara
asique, el dispositivo 18 corresponde a "escribir en consola", o salida estandard.
asique, el dispositivo 18 corresponde a "escribir en consola", o salida estándar.
¡nuestro programa está enviando el valor hexadecimal 68 (caracter 'h') a la salida estandard!
@ -330,6 +341,12 @@ puedes ver los valores hexadecimales de los caracteres ascii en la siguiente tab
=> https://wiki.xxiivv.com/site/ascii.html ascii table
### números literales
observe que los números literales que escribimos, 0100, 18 y 68, se escriben en hexadecimal utilizando 4 dígitos correspondientes a dos bytes, o 2 dígitos correspondientes a un byte.
en uxntal sólo podemos escribir números de 2 ó 4 dígitos hexadecimales. si, por ejemplo, sólo nos interesara escribir un único dígito hexadecimal, tendríamos que incluir un 0 a su izquierda.
## rom ensamblada
podemos ver que el ensamblador reporta que nuestro programa es de 5 bytes de tamaño:
@ -350,7 +367,6 @@ $ hexdump -C bin/hola.rom
¡osea, efectivamente, nuestro programa ensamblado presenta una correspondencia uno a uno con las instrucciones que acabamos de escribir!
de hecho, podríamos haber escrito nuestro programa con estos números hexadecimales (el código máquina), y hubiera funcionado igual:
```
@ -374,16 +390,14 @@ podemos expandir nuestro programa para imprimir más caracteres:
LIT 6f LIT 18 DEO ( o )
LIT 6c LIT 18 DEO ( l )
LIT 61 LIT 18 DEO ( a )
LIT 0a LIT 18 DEO ( newline )
LIT 0a LIT 18 DEO ( nuevalínea )
```
si lo ensamblamos y corremos, tendremos un 'hola' en nuestra terminal, usando 25 bytes de programa :)
¿ok, y... te gusta?
¿ok, y... te gusta? ¿parece sencillo? ¿quizás innecesariamente complejo?
¿parece innecesariamente complejo?
veremos ahora algunas virtudes de uxntal que hacen escribir y leer código más "confortable".
veremos algunas características de uxntal que hacen escribir y leer su código una experiencia más "cómoda".
# runas, etiquetas, macros
@ -399,7 +413,7 @@ si la dirección es de 2 bytes de longitud, es asumido que es una dirección de
## runa hex literal
hablemos de otra: #.
hablemos de otra runa: #.
éste caracter define un "hex literal": es básicamente un atajo para la instrucción LIT.
@ -410,8 +424,6 @@ usando esta runa, podemos reescribir nuestro primer programa como:
|0100 #68 #18 DEO
```
nota que sólo puedes usar esta runa para escribir los contenidos de uno o dos bytes (dos o cuatro nibbles).
el siguiente tendría el mismo comportamiento que el programa de arriba, pero usando un byte menos (en la siguiente sección/día veremos por qué)
```
@ -419,9 +431,13 @@ el siguiente tendría el mismo comportamiento que el programa de arriba, pero us
|0100 #6818 DEO
```
importante: recuerda que esta runa (y las otras con la palabra "literal" en su nombre) es un atajo para la instrucción LIT. esto puede prestarse a confusión en algunos casos :)
nota que sólo puedes usar esta runa para escribir los contenidos de uno o dos bytes (dos o cuatro nibbles).
## runa de caracter raw
importante: recuerda que esta runa (y las otras con la palabra "literal" en su nombre) es un atajo para la instrucción LIT. esto implica que uxn empujará estos valores hacia abajo en la pila. esto puede prestarse a confusión en algunos casos :)
si sólo queremos tener un número específico en la memoria principal, sin empujarlo a la pila, simplemente escribiríamos el número tal cual, "crudo". esta es la forma en que lo hicimos en nuestros primeros programas de arriba.
## runa de caracter "crudo" o "raw"
ésta es la runa de caracter raw: '
@ -435,11 +451,13 @@ nuestro "programa hola" luciría de la siguiente manera, usando las nuevas runas
LIT 'o #18 DEO
LIT 'l #18 DEO
LIT 'a #18 DEO
#0a #18 DEO ( newline )
#0a #18 DEO ( nuevalínea )
```
el "raw" en el nombre de esta runa indica que no es literal, por ejemplo que no agrega una instrucción LIT.
por eso debemos incluir una instrucción LIT.
## runas para etiquetas
incluso ahora que sabemos que #18 corresponde a empujar la dirección de dispositivo escribir en consola en la pila, por legibilidad y para asegurar nuestro código a futuro es una buena práctica asignar una serie de etiquetas que corresponderán a ese dispositivo y sus sub direcciones.
@ -449,25 +467,25 @@ la runa @ nos permite definir etiquetas, y la runa & nos permite definir sub eti
por ejemplo, para el dispositivo de consola, la manera en que verías esto escrito en programas para la computadora varvara es la siguiente:
```
|10 @Console [ &vector $2 &read $1 &pad $5 &write $1 &error $1 ]
|10 @Consola [ &vector $2 &lee $1 &pad $5 &escribe $1 &error $1 ]
```
podemos ver un pad absoluto a la dirección 10, que asigna lo siguiente a esa dirección. dado que la dirección consiste en un sólo byte, uxnasm asume que es para el espacio de memoria de entrada/salida o la página cero.
luego vemos la etiqueta @Console: ésta etiqueta va a corresponder a la dirección 10.
luego vemos la etiqueta @Consola: ésta etiqueta va a corresponder a la dirección 10.
los corchetes son ignorados, pero incluidos por legibilidad.
luego tenemos varias sub etiquetas, indicadas por la runa &, y pads relativos, indicados por la runa $. ¿cómo los leemos/interpretamos?
* la sub etiqueta &vector tiene la misma dirección que su etiqueta madre @Console: 10
* la sub etiqueta &vector tiene la misma dirección que su etiqueta madre @Consola: 10
* $2 salta dos bytes (podemos leer esto como &vector siendo una dirección a una palabra de dos bytes de longitud)
* la sub etiqueta &read tiene la dirección 12
* $1 salta un byte (&read sería una dirección para una palabra de 1 byte de longitud)
* la sub etiqueta &lee tiene la dirección 12
* $1 salta un byte (&lee sería una dirección para una palabra de 1 byte de longitud)
* la sub etiqueta &pad tiene la dirección 13
* $5 salta el resto de los bytes del primer grupo de 8 bytes del dispositivo: éstos bytes corresponden a las "entradas"
* la sub etiqueta &write tiene la dirección 18 (¡la que ya conocíamos!)
* $1 salta un byte (&write sería una dirección para una palabra de 1 byte de longitud)
* la sub etiqueta &escribe tiene la dirección 18 (¡la que ya conocíamos!)
* $1 salta un byte (&escribe sería una dirección para una palabra de 1 byte de longitud)
* la subetiqueta &error tiene la dirección 19
nada de esto sería traducido a código máquina, pero nos asiste al escribir código uxntal.
@ -481,29 +499,29 @@ podemos reescribir nuestro "programa hola mundo" como sigue:
```
( hola.tal )
( devices )
|10 @Console [ &vector $2 &read $1 &pad $5 &write $1 &error $1 ]
( dispositivos )
|10 @Consola [ &vector $2 &lee $1 &pad $5 &escribe $1 &error $1 ]
( programa principal )
|0100 LIT 'h .Console/write DEO
LIT 'o .Console/write DEO
LIT 'l .Console/write DEO
LIT 'a .Console/write DEO
#0a .Console/write DEO ( newline )
|0100 LIT 'h .Consola/escribe DEO
LIT 'o .Consola/escribe DEO
LIT 'l .Consola/escribe DEO
LIT 'a .Consola/escribe DEO
#0a .Consola/escribe DEO ( nuevalínea )
```
ahora esto empieza a parecerse más a los ejemplos que puedes encontrar en línea y/o en el repositorio uxn :)
## macros
siguiendo con la herencia de forth (?), en uxntal podemos definir nuestras propias "palabras" que nos permiten agrupar y reutilizar instrucciones.
siguiendo con la herencia de FORTH, en uxntal podemos definir nuestras propias "palabras" que nos permiten agrupar y reutilizar instrucciones.
durante el ensamblado, estos macros son (recursivamente) reemplazados por los contenidos de sus definiciones.
por ejemplo, podemos ver que el siguiente fragmento de código es repetido varias veces en nuestro programa.
```
.Console/write DEO ( equivalent to #18 DEO, or LIT 18 DEO )
.Consola/escribe DEO ( equivalente a #18 DEO, o a LIT 18 DEO )
```
podemos definir un macro llamado EMIT que tomará de la pila un byte correspondiente a un caracter, y lo imprimirá en la salida estandard. para esto, necesitamos la runa %, y llaves para la definición.
@ -512,7 +530,7 @@ podemos definir un macro llamado EMIT que tomará de la pila un byte correspondi
```
( escribe un caracter a la salida estandard )
%EMIT { .Console/write DEO } ( caracter -- )
%EMIT { .Consola/escribe DEO } ( caracter -- )
```
para llamar a un macro, sólo escribimos su nombre:
@ -536,11 +554,11 @@ usando todos estos macros y runas, nuestro programa puede terminar luciendo como
```
( hola.tal )
( dispositivos )
|10 @Console [ &vector $2 &read $1 &pad $5 &write $1 &error $1 ]
|10 @Consola [ &vector $2 &lee $1 &pad $5 &escribe $1 &error $1 ]
( macros )
( imprime un caracter en la salida estandard )
%EMIT { .Console/write DEO } ( caracter -- )
%EMIT { .Consola/escribe DEO } ( caracter -- )
( imprime una nueva línea )
%NL { #0a EMIT } ( -- )
@ -562,7 +580,7 @@ podemos "mejorar" este programa haciendo que un loop imprima los caracteres, per
en nuestro programa previo, el macro EMIT es llamado justo después de empujar un caracter a la pila.
¿cómo reescribirías el programa para empujar primero todos los caracteres, y luego "EMIT"ir todos ellos en una secuencia como ésta?
¿cómo reescribirías el programa para que primero empuje todos los caracteres y luego los emita (o haga "EMIT") todos con una secuencia como esta?
```
EMIT EMIT EMIT EMIT EMIT
@ -578,6 +596,12 @@ define un macro IMPRIMIR-DIGITO que toma un número (del 0 al 9) de la pila, e i
%IMPRIMIR-DIGITO { } ( número -- )
```
recordemos que el número tendría que ser escrito como un byte completo para que sea uxntal válido. si quisieras probar esta macro con, por ejemplo, el número 2, tendrías que escribirlo como 02:
```
#02 IMPRIMIR-DIGITO
```
# instrucciones del día 1
éstas son las instrucciones que cubrimos hoy:

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,772 @@
# 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 a nivel de bit (o "bitwise") significa que aplica la operación AND a cada uno de los bits de los operandos.
si ambas banderas fueran "verdaderas":
```
0000 0001 ( verdadero )
AND 0000 0001 ( verdadero )
----------
0000 0001 ( verdadero )
```
si alguno (o ambos) de los indicadores fuera "falso":
```
0000 0001 ( verdadero )
AND 0000 0000 ( falso )
----------
0000 0000 ( falso )
```
como estos indicadores sólo utilizan el bit menos significativo (el bit más a la derecha) para codificar su valor, un AND a nivel de bits es equivalente a un AND lógico convencional.
### OR
el siguiente código empujará una bandera hacia abajo en la pila si el byte tecla es '1' o 'a':
```
.Controlador/tecla DEI ( lee la tecla y la empuja a la pila )
LIT '1 EQU ( ¿es '1'? empuja la bandera a la pila )
.Controlador/tecla DEI ( lee la tecla y empujar a la pila )
LIT 'a EQU ( ¿es 'a'? empuja la bandera a la pila )
ORA ( aplica un OR a las banderas en la pila, y empuja el resultado en la pila )
```
cuando alguna o ambas banderas son verdaderas, la bandera será verdadera:
```
0000 0001 ( verdadero )
OR 0000 0000 ( falso )
----------
0000 0001 ( verdadero )
```
sólo cuando ambas banderas sean falsas, la bandera resultante será falsa.
### EOR
un 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&lt;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} :)

View File

@ -0,0 +1,971 @@
# 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_de_uxn_día_3.gmi {tutorial de 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_de_uxn_día_3.gmi {tutorial de 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_de_uxn_día_5.gmi {tutorial de 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} :)

View File

@ -0,0 +1,786 @@
# tutorial de uxn: día 5, el ratón y chucherías de uxntal
esta es la quinta sección del {tutorial de uxn}! aquí introducimos el dispositivo de ratón varvara para explorar más posibles interacciones, y cubrimos los elementos restantes de uxntal y uxn: la pila de retorno, el modo de retorno y el modo de retención.
también discutimos posibles estructuras para crear bucles y programas más complejos utilizando estos recursos.
# el dispositivo del ratón
el dispositivo del ratón en el ordenador varvara es similar al dispositivo controlador en varios aspectos: tiene un vector que es llamado con cualquier evento del ratón (cambio de estado de los botones, movimiento, movimiento de desplazamiento) y un par de bytes para comprobar su estado.
adicionalmente, tiene un par de cortos correspondientes a las coordenadas x,y del puntero del ratón.
vemos este dispositivo definido en uxntal de la siguiente manera:
```
|90 @Raton [ &vector $2 &x $2 &y $2 &estado $1 &pad $3 &despx $2 &despy $2 ]
```
## byte de estado
el byte de estado codifica el estado de activación/desactivación de hasta 8 botones del ratón; uno por bit.
contando de derecha a izquierda, el primer bit corresponde al primer botón del ratón, el segundo al segundo, y así sucesivamente.
normalmente, en un ratón de tres botones, el primer botón es el izquierdo, el segundo el del medio y el tercero el derecho.
utilizando un ratón de tres botones como este, tendríamos ocho valores posibles para el byte de estado, por ejemplo :
* 00 cuando no se pulsa ninguno de los botones
* 01 cuando sólo se pulsa el primer botón
* 02 cuando sólo se presiona el segundo botón
* 04 cuando sólo se pulsa el tercer botón
nota que al igual que el dispositivo controlador, este sistema nos permite comprobar si hay varios botones pulsados a la vez:
* 03 cuando el primer y el segundo botón están presionados
* 05 cuando se pulsan el primer y el tercer botón
* 06 cuando se pulsan el segundo y el tercer botón
* 07 cuando se pulsan los tres botones
recuerda que podemos utilizar las máscaras AND, tal y como se introdujo en {tutorial de uxn día 3}, para aislar y evaluar por separado cualquiera de estos bits.
## shorts de desplazamiento
el dispositivo del ratón tiene un par de cortos para indicar si el ratón se está desplazando.
despy indicará un desplazamiento vertical con un valor "positivo" cuando se mueve hacia arriba (o en realidad, "lejos del usuario"), y un valor "negativo" cuando se mueve hacia abajo (o "hacia el usuario")
* 0001 cuando se desplaza hacia arriba
* ffff cuando se desplaza hacia abajo
* 0000 cuando no se desplaza
del mismo modo, despx indicará un desplazamiento horizontal
* 0001 cuando se desplaza hacia la derecha
* ffff cuando se desplaza hacia la izquierda
* 0000 cuando no se desplaza
dependiendo del dispositivo, los valores pueden ser mayores que 0001 o menores que ffff dependiendo de la velocidad de desplazamiento.
## vector ratón
el vector del ratón se disparará en cualquiera de los siguientes eventos:
* se ha pulsado un botón
* se suelta un botón
* el ratón se ha movido
* la rueda del ratón se ha movido
## hola ratón
¡empecemos a dibujar!
el siguiente es un ejemplo simple que ilustra el uso de los siguientes elementos:
* vector del ratón
* coordenadas x e y del ratón
* estado del ratón (pulsado o no pulsado)
combinado con un condicional y el dibujo de un sprite.
dibuja nuestro cuadrado en la posición del ratón, cambiando su color cuando se pulsa cualquier botón del ratón.
=> ./img/screenshot_uxn-draw-with-mouse.png captura de pantalla que muestra un dibujo realizado con el programa: líneas onduladas compuestas por cuadrados superpuestos de dos colores diferentes
```
( hola-raton.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 ]
|90 @Raton [ &vector $2 &x $2 &y $2 &estado $1 &pad $3 &despx $2 &despy $2 ]
( init )
|0100
( establecer colores del sistema )
#2ce9 .Sistema/r DEO2
#01c0 .Sistema/g DEO2
#2ce5 .Sistema/b DEO2
( establecer vector del ratón )
;en-raton .Raton/vector DEO2
( establecer dirección del sprite )
;cuadrado .Pantalla/direc DEO2
BRK
@en-raton ( -> )
.Raton/x DEI2 .Pantalla/x DEO2
.Raton/y DEI2 .Pantalla/y DEO2
( salta si se pulsa algún botón )
.Raton/estado DEI ,&presionado JCN
( dibujar sprite usando el color 2 y 0 en el fondo )
#02 .Pantalla/sprite DEO
BRK
&presionado
( dibujar sprite usando el color 1 y 0 en el fondo )
#01 .Pantalla/sprite DEO
BRK
@cuadrado [ ff81 8181 8181 81ff ]
```
## hola puntero
tal vez hayas notado que, antes de hoy, el puntero del ratón ha desaparecido al entrar en la ventana de uxnemu.
¿cómo podríamos programar y replicar su comportamiento en uxntal?
podríamos utilizar una estrategia similar a la que hicimos para animar un sprite:
* borrar el sprite de la posición anterior
* actualizar la posición
* dibujar el sprite en la nueva posición
este procedimiento puede ocurrir cada vez que se dispara el vector del ratón.
podemos utilizar un conjunto de variables en la página cero para almacenar la posición del puntero, de forma que tengamos una forma de limpiar el sprite en las coordenadas anteriores del ratón.
adicionalmente, aquí tenemos los datos de un sprite de 1bpp de un puntero de ratón, tomados de los ejemplos de uxn:
```
@puntero_icn [ 80c0 e0f0 f8e0 1000 ]
```
### el programa
este es un programa que logra dibujar el puntero en la pantalla
```
( hola-puntero.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 ]
|90 @Raton [ &vector $2 &x $2 &y $2 &estado $1 &pad $3 &despx $2 &despy $2 ]
( página cero )
|0000
@puntero [ &x $2 &y $2 ]
( init )
|0100
( establecer colores del sistema )
#2ce9 .Sistema/r DEO2
#01c0 .Sistema/g DEO2
#2ce5 .Sistema/b DEO2
( establecer el vector del ratón )
;en-raton .Raton/vector DEO2
( establecer dirección del sprite )
;puntero_icn .Pantalla/direc DEO2
BRK
@en-raton ( -> )
( envía la posición del puntero a la pantalla )
.puntero/x LDZ2 .Pantalla/x DEO2
.puntero/y LDZ2 .Pantalla/y DEO2
( borrar el sprite del primer plano )
#40 .Pantalla/sprite DEO
( actualizar la posición del puntero )
.Raton/x DEI2 .puntero/x STZ2
.Raton/y DEI2 .puntero/y STZ2
( enviar la posición del puntero a la pantalla )
.puntero/x LDZ2 .Pantalla/x DEO2
.puntero/y LDZ2 .Pantalla/y DEO2
( dibujar sprite con color 2 en primer plano )
#4a .Pantalla/sprite DEO
BRK
@puntero_icn [ 80c0 e0f0 f8e0 1000 ]
```
nótese que dibuja el puntero en primer plano, y que utiliza 'a' en el nibble bajo del byte del sprite: esto implica que utilizará el color 2 para dibujar la forma del puntero, y dibujará con transparencia el resto del tile. ( ver dibujar sprites de 1bpp en {tutorial de uxn día 2} )
este modo de mezcla te permitiría dibujar cosas en el plano de fondo y que el puntero las cubra sólo con su forma, y no con todo el cuadrado del tile.
¡te invito a que lo pruebes!
¡dibuja algunas cosas en el fondo y ve cómo se ve el puntero, tal y como está ahora. luego reemplaza el byte del sprite del puntero con, por ejemplo, 42 para ver la diferencia!
### algunos problemas
ahora, podemos ver que el programa funciona, pero está inundando la subrutina en-raton con mucho código.
eso es aun mas cierto si consideramos que solo estamos dibujando el puntero, y no tomando ninguna otra acción, como responder a los botones.
crear una macro para todo este código podría ser posible, pero también poco práctico debido a la cantidad de código.
¿tal vez podríamos tener un JMP a otra sección del programa, que al final tiene otro JMP para volver a la posición correspondiente en la subrutina en-raton?
de hecho, eso es casi lo que haremos, pero con un elemento adicional de uxn: ¡su pila de retorno!
# la pila de retorno
hasta ahora, "la pila" que hemos estado utilizando es lo que se suele llamar "la pila de trabajo".
uxn, al igual que otros sistemas tipo forth, tiene otra pila del mismo tamaño, "la pila de retorno".
¿por qué se llama así?
la idea es que se utiliza normalmente para introducir en ella direcciones de memoria de programa.
estas direcciones corresponderían a ubicaciones a las que quisiéramos eventualmente "regresar".
## preparando nuestro programa
en primer lugar, vamos a mover nuestra subrutina de dibujo de punteros a otra etiqueta de nuestro programa:
```
@dibuja-puntero ( -- )
( enviar la posición del puntero a la pantalla )
.puntero/x LDZ2 .Pantalla/x DEO2
.puntero/y LDZ2 .Pantalla/y DEO2
( borrar el sprite del primer plano )
#40 .Pantalla/sprite DEO
( actualizar la posición del puntero )
.Raton/x DEI2 .puntero/x STZ2
.Raton/y DEI2 .puntero/y STZ2
( enviar la posición del puntero a la pantalla )
.puntero/x LDZ2 .Pantalla/x DEO2
.puntero/y LDZ2 .Pantalla/y DEO2
( dibujar sprite con color 2 en primer plano )
#4a .Pantalla/sprite DEO
BRK
```
notemos que podríamos unir las acciones de actualizar la posición del puntero y enviarla a la pantalla, usando un par de DUP2:
```
( actualizar la posición del puntero y enviarla a la pantalla )
.Raton/x DEI2 DUP2 .puntero/x STZ2 .Pantalla/x DEO2
.Raton/y DEI2 DUP2 .puntero/y STZ2 .Pantalla/y DEO2
```
esto dejaría nuestra subrutina en-raton vacía:
```
@en-raton( -> )
BRK
```
### usando saltos normales
con lo que ya sabemos, y dependiendo de la posición de dibuja-puntero con respecto a en-raton, podríamos hacer un salto relativo:
```
@en-raton ( -> )
,dibuja-puntero JMP
&retorno
( algo más aquí )
BRK
```
o un salto absoluto:
```
@en-raton ( -> )
;dibuja-puntero JMP2
&retorno
( algo más aquí )
BRK
```
la cosa es que si queremos que ocurra algo más después de dibujar el puntero dentro de en-raton, no podemos volver atrás fácilmente.
al final de nuestra subrutina dibuja-puntero, necesitaríamos "saltar hacia atrás" así
```
;en-raton/volver JMP2
```
funcionaría, pero no es la mejor forma.
¡conozcamos una alternativa, la instrucción "saltar y almacenar"!
# salta y almacena o "jump and stash"
la instrucción de salto y stash, JSR, hace lo mismo que JMP (saltar incondicionalmente a la dirección presente en la pila de trabajo), con la acción adicional de empujar hacia abajo en la pila de retorno la dirección absoluta de lo que sería la siguiente instrucción en memoria después de JSR.
en el modo normal, JSR toma una dirección relativa (un byte) de la pila de trabajo, y en el modo corto, JSR2 toma una dirección absoluta.
en ambos casos, JSR empujará una dirección absoluta (2 bytes) hacia abajo en la pila de retorno.
## salto relativo
por ejemplo, nuestros saltos podrían reescribirse de la siguiente manera. en el caso de un salto relativo:
```
@en-raton ( -> )
,dibuja-puntero JSR
( algo más aquí )
BRK
```
## salto absoluto
y en el caso de un salto absoluto:
```
@en-raton ( -> )
;dibuja-puntero JSR2
( algo más aquí )
BRK
```
## volviendo
JSR está empujando la "dirección de retorno" hacia la pila de retorno.
ahora la pregunta es: ¿cómo tomamos la dirección de esa pila, para saltar allí?
¡tendríamos que usar el "modo de retorno"!
# el modo de retorno
similar al modo corto, el modo de retorno en uxn consiste en activar una bandera binaria en el byte que codifica una instrucción.
el modo de retorno se codifica en el 7mo bit de un byte de instrucción, contando de derecha a izquierda.
siempre que se active esta bandera, uxn realizará la instrucción dada pero utilizando como fuentes el contenido de la pila de retorno en lugar del contenido de la pila de trabajo.
en uxntal, indicamos que queremos activar esta bandera añadiendo la letra 'r' al final de un mnemónico de instrucción.
como cada uno de los modos es un bit independiente, es posible combinarlos, por ejemplo, activando el modo de retorno y el modo corto utilizando el sufijo '2r'.
## un breve ejemplo de pila de retorno
por ejemplo, el siguiente código empujaría dos números hacia abajo en la pila de retorno, los sumaría, y empujaría el resultado de nuevo en la pila de retorno:
```
LITr 01 LITr 02 ADDr
```
o, combinando los modos corto y de retorno en la instrucción LIT:
```
LIT2r 0102 ADDr
```
ahora volvamos a nuestros saltos :)
## saltando al retorno
como ya hemos discutido, JMP nos permitirá saltar incondicionalmente a la dirección dada en la parte superior de la pila (de trabajo).
JSR o JSR2 empujan hacia abajo en la pila de retorno la dirección absoluta de la siguiente instrucción, un short, para que eventualmente podamos retornar allí.
¿cómo podemos saltar incondicionalmente a esa dirección absoluta que está presente en la pila de retorno?
¡exactamente!
¡activando el modo de retorno en la instrucción JMP!
adicionalmente, como las direcciones empujadas por JSR son shorts, necesitamos activar el modo short también:
```
JMP2r ( saltar a la dirección absoluta en la parte superior de la pila de retorno )
```
en muchos programas uxntales verás esta instrucción escrita como una macro, RTN ( volver o "return" )
```
%RTN { JMP2r }
```
podemos terminar una subrutina usando esta macro para "volver" a la posición en el programa después del JSR correspondiente.
## ejemplo completo usando subrutinas
este es el programa hola-puntero.tal, pero utilizando dibuja-puntero como subrutina que se " invoca" con JSR2 y que termina con RTN:
```
( hola-puntero.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 ]
|90 @Raton [ &vector $2 &x $2 &y $2 &estado $1 &pad $3 &despx $2 &despy $2 ]
( macros )
%RTN { JMP2r }
( página cero )
|0000
@puntero [ &x $2 &y $2 ]
( init )
|0100
( establecer colores del sistema )
#2ce9 .Sistema/r DEO2
#01c0 .Sistema/g DEO2
#2ce5 .Sistema/b DEO2
( establecer el vector del ratón )
;en-raton .Raton/vector DEO2
( establecer dirección del sprite )
;puntero_icn .Pantalla/direc DEO2
BRK
@en-raton ( -> )
;dibujar-puntero JSR2 ( o ,dibujar-puntero JSR )
( algo más )
BRK
@dibuja-puntero ( -- )
( envía la posición del puntero a la pantalla )
.puntero/x LDZ2 .Pantalla/x DEO2
.puntero/y LDZ2 .Pantalla/y DEO2
( borrar el sprite del primer plano )
#40 .Pantalla/sprite DEO
( actualizar la posición del puntero )
.Raton/x DEI2 .puntero/x STZ2
.Raton/y DEI2 .puntero/y STZ2
( enviar la posición del puntero a la pantalla )
.puntero/x LDZ2 .Pantalla/x DEO2
.puntero/y LDZ2 .Pantalla/y DEO2
( dibujar sprite con color 2 en primer plano )
#4a .Pantalla/sprite DEO
RTN
@puntero_icn [ 80c0 e0f0 f8e0 1000 ]
```
observe que la etiqueta dibuja-puntero va acompañada de la notación de estado de la pila ( -- ) para indicar que, en este caso, no consume ni produce contenidos desde o hacia la pila de trabajo.
también note como esta subrutina termina con un RTN (JMP2r) que indica que el flujo del programa volverá a la posición posterior a la llamada de la subrutina.
## notas sobre subrutinas como "funciones"
tengamos en cuenta que una posible forma de enviar "argumentos" a una subrutina sería empujarlos hacia abajo en la pila de trabajo antes de llamarla.
además, una posible forma de que una subrutina "devuelva" sus resultados sería que los empujara hacia abajo en la pila de trabajo.
estos elementos pueden entonces ser consumidos desde la pila de trabajo después de regresar de la subrutina.
puede haber otros casos en los que el uso de "variables" tendría un sentido más lógico y/o legible para pasar argumentos y resultados.
# almacena, no saltes
habiendo introducido la pila de retorno y el modo de retorno, se nos abre otro mundo de posibilidades: también podemos utilizar la pila de retorno como una pila adicional y temporal, para almacenar algunos valores mientras operamos con otros.
para lograr esto, uxn tiene una instrucción llamada STH, almacenar o "stash".
¡esta es la última instrucción de uxn que teníamos que cubrir en esta serie de tutoriales! :)
## STH
STH toma un valor de la pila de trabajo y lo empuja hacia abajo en la pila de retorno.
en modo retorno, STHr hace lo contrario: toma un valor de la pila de retorno y lo empuja hacia abajo en la pila de trabajo.
y, como habrás previsto, en el modo corto esta instrucción opera moviendo cortos en lugar de bytes.
## ejemplo: línea horizontal
el siguiente es un ejemplo de subrutina que muestra algunas posibilidades de la pila y el modo de retorno.
se trata de una subrutina que dibuja una línea horizontal de una longitud dada (de 1 a 255 píxeles, es decir, utilizando un byte), partiendo de una coordenada x dada (corto) y utilizando una coordenada y dada (corto).
estos parámetros se dan como argumentos en la pila de trabajo.
la subrutina utiliza la pila de retorno para "almacenar" uno de estos argumentos mientras trabaja con los otros.
además, tiene un bucle de trabajo! escrito en una de las varias formas de implementarlo :)
el estado de las pilas de trabajo (pt) y de retorno (pr) se muestra en los comentarios después de casi cada paso. la parte superior de las pilas se encuentra a su derecha.
un signo de intercalación (^) después de un nombre de valor indica que corresponde a un cortocircuito.
```
@dibujar-linea-horizontal ( x^ y^ longitud -- )
( inicio )
( pt: x^ y^ longitud / pr: )
( almacenar la longitud en la pila de retorno )
STH ( pt: x^ y^ / pr: longitud )
( fijar las coordenadas iniciales )
.Pantalla/y DEO2 ( pt: x^ / pr: longitud )
.Pantalla/x DEO2 ( pt: / pr: longitud )
( recuperar la longitud de la pila de retorno )
STHr ( pt: longitud / pr: )
( iniciar el conteo )
#00 ( pt: longitud 00 / pr: )
&bucle
( almacenar la longitud y el conteo )
STH2 ( pt: / pr: longitud conteo )
( dibujar pixel con color 2 )
#02 .Pantalla/pixel DEO
( incrementar x )
.Pantalla/x DEI2 INC2 .Pantalla/x DEO2
( recupera la longitud y el conteo )
STH2r ( pt: longitud conteo / pr: )
( incrementa el conteo para obtener el nuevo conteo )
INC ( pt: longitud conteo / pr: )
( duplicar longitud y conteo, comparar y saltar )
DUP2 ( pt: longitud conteo / pr: )
NEQ ( pt: longitud conteo bandera / pr: )
,&bucle JCN ( pt: longitud conteo / pr: )
POP2 ( pt: / pr: )
RTN
```
### llamando
para llamar a la subrutina, podrías hacer algo como lo siguiente
```
#0008 ( empujar x inicial )
.Pantalla/altura DEI2 HALF2 ( empujar y )
#ff ( empujar longitud de la línea )
;dibujar-linea-horizontal JSR2 ( llamar subrutina )
```
### notas
nota que en esta subrutina específica, el uso de STH2 y STH2r después de la sub-etiqueta &bucle no es realmente necesario: las operaciones entre estas instrucciones sí tocan la pila de trabajo pero después la dejan como estaba.
sin embargo, muestra cómo podemos usar estas instrucciones para tener una pila de trabajo limpia sin que otros valores interfieran.
### posibles ejercicios
* hacer que la subrutina dibuje una línea hecha de sprites en lugar de píxeles individuales
* modificar la subrutina para que pueda recibir como argumento (en la pila de trabajo) el color de los sprites o píxeles
* modificar la subrutina para que pueda recibir como argumento (en la pila de trabajo) la dirección del sprite a dibujar
* reescribir la subrutina para que utilice una longitud de corto para la línea, en lugar de un byte.
# el modo mantener
el último elemento básico de uxntal que nos queda por cubrir es su tercer modo para las instrucciones: el modo mantener o "keep".
el modo mantener se codifica en el 8º bit de un byte de instrucción, contando de derecha a izquierda.
en uxntal, indicamos que queremos activar esta bandera añadiendo la letra 'k' al final de un mnemónico de instrucción.
siempre que se active esta bandera, uxn realizará la instrucción dada pero "manteniendo" los valores originales en la pila correspondiente.
en otras palabras, en el modo mantener los elementos no serán consumidos de la pila, pero los resultados correspondientes serán empujados hacia abajo en la pila.
el modo mantener puede combinarse con los otros modos, para un total de ocho combinaciones posibles de modos.
## modo mantener en aritmética
sabemos lo que hace el siguiente código uxntal; empuja 01 y 02 hacia abajo en la pila, suma ambos elementos, y empuja el resultado (03) hacia abajo en la pila:
```
#01 #02 ( pt: 01 02 )
ADD ( pt: 03 )
```
compáralo con lo que ocurre al usar ADDk en su lugar:
```
#01 #02 ( pt: 01 02 )
ADDk ( pt: 01 02 03 )
```
la suma se realiza y el resultado es empujado, pero los operandos se dejan en la pila.
puede ser difícil pensar en general en un uso para esto, ¡pero "mantenlo" en mente!
### módulo
en realidad, si recuerdas, en el {tutorial de uxn día 4} compartí contigo un par de macros para realizar una operación de módulo:
```
%MOD { DUP2 DIV MUL SUB } ( a b -- a%b )
%MOD2 { OVR2 OVR2 DIV2 MUL2 SUB2 } ( a b -- a%b )
```
dije entonces que había un conjunto más optimizado, y que lo discutiríamos más adelante.
¡ahora es ese momento!
en primer lugar, analicemos lo que ocurre con MOD. está calculando lo que se escribiría en notación infija de la siguiente manera, suponiendo que la barra (/) indica una división entera
```
a - ( a/b )*b
```
pruebe, por ejemplo, sustituir "a" por 7 y "b" por 3; el módulo o remanente debería ser 1
* a/b nos da el resultado de la división entera: 7/3 es 2
* (a/b)*b intenta obtener 'a' de nuevo, pero posiblemente "falla" debido al resto que se perdió en la división: 2*3 es 6
* a - (a/b)*b calcula cuál es la diferencia entre 'a' y el resultado obtenido: 7-6 es 1
en nuestra macro original, lo que ocurre es lo siguiente:
```
#07 #03 ( pt: 07 03 )
DUP2 ( pt: 07 03 07 03 )
DIV ( pt: 07 03 02 )
MUL ( pt: 07 06 )
SUB ( pt: 01 )
```
¿ves la posibilidad de introducir el modo mantener?
si te fijas bien, verás que el DUP2 está ahí para no perder los valores originales en la división, y así poder realizar la multiplicación y la resta después.
pero entonces, ¿cómo podemos realizar la división sin perder sus operandos y sin usar DUP2?
¡así es!
DUP2 DIV es equivalente a... ¡DIVk! ¡una división que no pierde sus operandos!
```
#07 #03 ( pt: 07 03 )
DIVk ( pt: 07 03 02 )
MUL ( pt: 07 06 )
SUB ( pt: 01 )
```
¡de esta manera, nuestra macro puede tener un byte menos!
podemos generalizar este comportamiento para el modo corto, y obtener el conjunto óptimo de macros que mencioné anteriormente:
```
%MOD { DIVk MUL SUB }
%MOD2 { DIV2k MUL2 SUB2 }
```
## modo mantener y comparaciones
el modo mantener puede ser útil cuando hacemos comparaciones y no queremos perder los valores originales.
por ejemplo, en nuestra subrutina dibujar-línea-horizontal, teníamos el siguiente conjunto de líneas de código:
```
( duplicar longitud y el conteo, comparar, y saltar )
DUP2 ( pt: longitud conteo longitud conteo / pr: )
NEQ ( pt: longitud conteo bandera / pr: )
,&bucle JCN ( pt: longitud conteo / pr: )
```
verás que aquí, como en el caso DIVk anterior, el DUP2 está ahí sólo para asegurarse de que la longitud y el recuento no se pierden al realizar NEQ.
por lo tanto, podríamos reemplazar DUP2 NEQ con NEQk:
```
( duplicar la longitud y el conteo, comparar, y saltar )
NEQk ( pt: longitud conteo bandera / pr: )
,&bucle JCN ( pt: longitud conteo / pr: )
```
## el modo mantener y la pila de retorno
a veces queremos almacenar una copia de un valor que usaremos en el momento.
sin el modo mantener, escribiríamos:
```
DUP STH ( duplicar y almacenar )
```
con el modo mantener, podemos escribir:
```
STHk ( almacenar y mantener )
```
del mismo modo, habrá ocasiones en las que queramos recuperar una copia de un valor de la pila de retorno.
para ello, podemos escribir:
```
STHkr ( recuperar una copia de la pila de retorno )
```
## más y más modo mantener
se siguen encontrando nuevos e interesantes usos para el modo mantener :)
¡no dudes en compartirlos con nosotres!
# más ejercicios
con lo que ya hemos cubierto, y en caso de que quieras algunas ideas, aquí hay algunas cosas que ahora deberían ser más fáciles de intentar construir:
## sprites de varios tiles
dibujar un sprite formado por varios tiles es un proceso que puede beneficiarse del uso de subrutinas: ten una subrutina que reciba un par de coordenadas x,y en la pila de trabajo cuando sea llamada, y úsala para dibujar los tiles en las posiciones correspondientes relativas a esas coordenadas.
## herramienta de dibujo
¡muchas posibilidades aquí!
tal vez comienza con dibujar sólo cuando se presiona un botón. cambia el color y/o el "pincel" dependiendo del botón que se presiona.
puedes tener diferentes "modos" seleccionables: tal vez cambien el pincel que estás usando, la forma en que el pincel se comporta (por ejemplo, ¿en espejo, caleidoscopio?), y/o las formas que se dibujan.
considera cómo seleccionarías esos modos: ¿botones en pantalla? ¿teclas del teclado? ¿acordes con los botones del ratón?
ten en cuenta que puedes cambiar el vector de un dispositivo durante el tiempo de ejecución: podrías tener una subrutina en-raton diferente dependiendo del modo que hayas seleccionado :)
¿cómo podrías utilizar la rueda del ratón como ayuda para dibujar?
## y más...
básicamente, las puertas para las aplicaciones visuales interactivas en el ordenador varvara están completamente abiertas ahora para ti :)
¿crearás juegos? ¿pequeñas aplicaciones, útiles o no? ¿un instrumento para visuales en vivo? ¿programas dirigidos a dispositivos manuales específicos?
algunas cosas pueden parecer difíciles de construir, pero afortunadamente, por ahora no hay nada más en el funcionamiento de la máquina que no hayamos cubierto ya.
puedes ir poco a poco, paso a paso, practicando tu manejo de la pila y ejercitando el cerebro postfijo, y llegarás a donde quieras :)
¡nos encantaría ver lo que creas!
# instrucciones del día 5
estas son las instrucciones uxntales que hemos discutido hoy! con estas, las hemos cubierto todas!
* JSR: salta incondicionalmente a la dirección de la pila de trabajo, empujando hacia abajo en la pila de retorno la dirección de la siguiente instrucción en memoria
* STH: toma un valor de la pila de trabajo y lo empuja hacia abajo en la pila de retorno. en el modo de retorno, hace lo contrario.
# día 6
en el {tutorial de uxn día 6} hablamos de cómo podemos integrar todo lo que hemos cubierto para crear subrutinas y programas aún más complejos para el ordenador varvara.
¡basamos nuestra discusión en una recreación del clásico juego pong!
además de utilizar estrategias y fragmentos de código anteriores, cubrimos estrategias para dibujar y controlar los sprites de varios tiles, y para comprobar las colisiones.
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} :)

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,7 @@
welcome to this beginner's, slow-paced and comprehensive guide for programming the varvara computer based on the {uxn} core.
the tutorial sections are divided in days as it can be followed along with a workshop.
you can get an offline version of this guide as the {introduction to uxn programming book}!
if you prefer video, you can watch a short {intro to uxn programming} workshop that we taught as an introduction.

View File

@ -160,11 +160,11 @@ the instruction will normally imply a change in the stack(s), and sometimes it m
in order to run varvara locally and off the grid, we'd have to get the uxn assembler (uxnasm) and emulator (uxnemu) from their git repository:
=> https://git.sr.ht/~rabbits/uxn ~rabbits/uxn
=> https://git.sr.ht/~rabbits/uxn ~rabbits/uxn - sourcehut git
you can either build these tools from source, or download pre-compiled binaries for multiple platforms.
you can find the installation instrutions in the repository.
you can find the installation instructions in the repository.
if you need a hand, find us in #uxn on irc.esper.net :)

View File

@ -6,32 +6,56 @@ we also discuss possible structures to create loops and more complex programs us
# the mouse device
the mouse device in the varvara computer is similar to the controller device in several ways: it has a vector that is called with any mouse event (change of buttons state, movement, wheel movement) and a couple of bytes to check its state.
the mouse device in the varvara computer is similar to the controller device in several ways: it has a vector that is called with any mouse event (change of buttons state, movement, scroll movement) and a couple of bytes to check its state.
additionally, it has a couple of shorts corresponding to the x,y coordinates of the mouse pointer.
we see this device defined in uxntal in the following way:
```
|90 @Mouse [ &vector $2 &x $2 &y $2 &state $1 &wheel $1 ]
|90 @Mouse [ &vector $2 &x $2 &y $2 &state $1 &pad $3 &scrollx $2 &scrolly $2 ]
```
## state byte
the state byte has these possible values:
the state byte encodes the on/off state of up to 8 buttons in the mouse; one per bit.
* 01 when the left button is pressed
* 10 when the right button is pressed
* 11 when both buttons are pressed
counting from right to left, the first bit corresponds to the first mouse button, the second bit to the second mouse button, and so on.
usually, in a three-button mouse, the first button is the left one, the second button the middle one, and the third button the right one.
using a three-button mouse like this, we would have eight possible values for the state byte, for example:
* 00 when none of the buttons are pressed
* 01 when only the first button is pressed
* 02 when only the second button is pressed
* 04 when only the third button is pressed
## wheel byte
note that similarly to the controller device, this system allows us to check for several buttons pressed at once:
the wheel byte has these possible values:
* 03 when the first and second buttons are pressed
* 05 when the first and third buttons are pressed
* 06 when the second and third buttons are pressed
* 07 when the threee buttons are pressed
* 01 when the mouse wheel is moving upwards
* ff when the mouse wheel is moving downwards
* 00 when the mouse wheel is not moving
remember that we can use AND masks, as introduced on {uxn tutorial day 3}, to isolate and evaluate separately any of these bits.
## scroll shorts
the mouse device has a couple of shorts to indicate if the mouse is scrolling.
scrolly will indicate a vertical scroll with a "positive" value when moving upwards (or actually, "away from the user"), and a "negative" value moving downwards (or "towards the user")
* 0001 when scrolling upwards
* ffff when scrolling downwards
* 0000 when not moving
similarly, scrollx will indicate an horizontal scroll
* 0001 when scrolling to the right
* ffff when scrolling to the left
* 0000 when not moving
depending on the device, the values can be greater than 0001 or less than ffff depending on the speed of the scroll.
## mouse vector
@ -46,7 +70,7 @@ the mouse vector will be fired in any of the following events:
let's start drawing!
the following is a simple example that illustrates the use of
the following is a simple example that illustrates the use of the following elements:
* mouse vector
* mouse x and y coordinates
@ -64,7 +88,7 @@ it draws our square in the position of the mouse, changing its color when any mo
( devices )
|00 @System [ &vector $2 &pad $6 &r $2 &g $2 &b $2 ]
|20 @Screen [ &vector $2 &width $2 &height $2 &pad $2 &x $2 &y $2 &addr $2 &pixel $1 &sprite $1 ]
|90 @Mouse [ &vector $2 &x $2 &y $2 &state $1 &wheel $1 ]
|90 @Mouse [ &vector $2 &x $2 &y $2 &state $1 &pad $3 &scrollx $2 &scrolly $2 ]
( init )
|0100
@ -85,7 +109,7 @@ BRK
.Mouse/x DEI2 .Screen/x DEO2
.Mouse/y DEI2 .Screen/y DEO2
( jump if a button is pressed )
( jump if any button is pressed )
.Mouse/state DEI ,&pressed JCN
( draw sprite using color 2 and 0 in background )
@ -116,7 +140,7 @@ this procedure can happen whenever the mouse vector is fired.
we can use a set of variables in the zero page in order to store the pointer position, so that we have a way of clearing the sprite in the previous coordinates of the mouse.
additionally, here we have a 1bpp sprite of a mouse pointer, taken from the uxn examples:
additionally, here we have the data of a 1bpp sprite of a mouse pointer, taken from the uxn examples:
```
@pointer_icn [ 80c0 e0f0 f8e0 1000 ]
@ -132,7 +156,7 @@ this is a program that accomplishes drawing the pointer on the screen!
( devices )
|00 @System [ &vector $2 &pad $6 &r $2 &g $2 &b $2 ]
|20 @Screen [ &vector $2 &width $2 &height $2 &pad $2 &x $2 &y $2 &addr $2 &pixel $1 &sprite $1 ]
|90 @Mouse [ &vector $2 &x $2 &y $2 &state $1 &wheel $1 ]
|90 @Mouse [ &vector $2 &x $2 &y $2 &state $1 &pad $3 &scrollx $2 &scrolly $2 ]
( zero page )
|0000
@ -182,7 +206,7 @@ this blending mode would allow you to draw things in the background and have the
i invite you to try this!
draw in the background and see how the pointer looks, and then replace its sprite byte with e.g. 42 to see the difference!
draw some things in the background and see how the pointer looks as it is now. then replace the pointer's sprite byte with e.g. 42 to see the difference!
### some issues
@ -192,7 +216,7 @@ that's even more true if we consider that we are only drawing the pointer, and n
creating a macro for all this code could be possible, but also kind of impractical due to the amount of code.
maybe we could have a JMP to another section of the program, that at its ends has another JMP to return to the corresponding position in the on-mouse subroutine?
maybe we could have a JMP to another section of the program, that at its end has another JMP to return to the corresponding position in the on-mouse subroutine?
actually, that's almost what we will do, but with an additional element of uxn: its return stack!
@ -233,6 +257,13 @@ first of all, let's move our pointer drawing subroutine to another label in our
#4a .Screen/sprite DEO
BRK
```
note that we could join the actions of updating the pointer position and sending it to the screen, using a pair of DUP2:
```
( update pointer position and send to screen )
.Mouse/x DEI2 DUP2 .pointer/x STZ2 .Screen/x DEO2
.Mouse/y DEI2 DUP2 .pointer/y STZ2 .Screen/y DEO2
```
this would leave our on-mouse subroutine empty:
@ -242,13 +273,6 @@ this would leave our on-mouse subroutine empty:
BRK
```
note that we could join the update pointer position with sending it to the screen, using a pair of DUP2:
```
( update pointer position and send to screen )
.Mouse/x DEI2 DUP2 .pointer/x STZ2 .Screen/x DEO2
.Mouse/y DEI2 DUP2 .pointer/y STZ2 .Screen/y DEO2
```
### using normal jumps
@ -282,14 +306,18 @@ at the end of our draw-pointer subroutine, we'd need to "jump back" like this:
;on-mouse/return JMP2
```
it would work, but it's not the best approach.
let's meet an alternative, the "jump and stash" instruction!
# jump and stash
the jump and stash instruction, JSR, does the same as JMP (unconditionally jump to the address present in the working stack), with the additional action of pushing down into the return stack the address of the next instruction in memory.
the jump and stash instruction, JSR, does the same as JMP (unconditionally jump to the address present in the working stack), with the additional action of pushing down into the return stack the absolute address of what would be the next instruction in memory after the JSR.
in the normal mode, JSR takes a relative address (one byte) from the working stack, and in short mode, JSR2 takes an absolute address.
in both of these cases, JSR will push an absolute address (2 bytes) down into the return stack.
## relative jump
for example, our jumps could be re-written as follows. in the case of a relative jump:
@ -334,13 +362,29 @@ in uxntal, we indicate that we want to set this flag adding the letter 'r' to th
as each of the modes is an independent bit, it is possible to combine them, e.g. activating the return mode and the short mode by using the suffix '2r'.
## a brief return stack example
for example, the following code would push two numbers down into the return stack, add them, and push the result back into the return stack:
```
LITr 01 LITr 02 ADDr
```
or, combining the short and return modes in the LIT instruction:
```
LIT2r 0102 ADDr
```
now let's go back to our jumps :)
## jumping to return
as we have discussed already, JMP will allow us to unconditionally jump to the address given in the top of the (working) stack.
JSR or JSR2 push down into the return stack the absolute address of the next instruction, a short, so that we can eventually return there.
how can we unconditionally jump to that absolute address in the return stack?
how can we unconditionally jump to that absolute address that is present in the return stack?
exactly!
@ -352,7 +396,7 @@ additionally, as the addresses pushed by JSR are shorts, we need to activate the
JMP2r ( jump to the absolute address at the top of the return stack )
```
normally, in uxntal programs you will see this instruction written as a macro, RTN (return)
in many uxntal programs you will see this instruction written as a macro, RTN (return)
```
%RTN { JMP2r }
@ -370,7 +414,7 @@ this is the hello-pointer.tal program, but using draw-pointer as a subroutine th
( devices )
|00 @System [ &vector $2 &pad $6 &r $2 &g $2 &b $2 ]
|20 @Screen [ &vector $2 &width $2 &height $2 &pad $2 &x $2 &y $2 &addr $2 &pixel $1 &sprite $1 ]
|90 @Mouse [ &vector $2 &x $2 &y $2 &state $1 &wheel $1 ]
|90 @Mouse [ &vector $2 &x $2 &y $2 &state $1 &pad $3 &scrollx $2 &scrolly $2 ]
( macros )
%RTN { JMP2r }
@ -425,11 +469,15 @@ RTN
note that the draw-pointer label is accompanied by the stack state notation ( -- ) to indicate that, in this case, it doesn't consume or produce contents from or to the working stack.
also note how this subroutine ends with a RTN (JMP2r) that indicates that the flow of the program will return to the position after the subroutine was called.
## notes on subroutines as "functions"
keep in mind that a possible way of sending "arguments" to a subroutine would be to push them down into the working stack before calling it.
additionaly, a possible way of receiving the results of a subroutine would be to have it pushing them down into the working stack so that they are there to be consumed after returning.
additionaly, a possible way for a subroutine to "return" its results would be to have it pushing them down into the working stack.
these elements can then be consumed from the working stack after returning from the subroutine.
there might be other instances where using "variables" would make more logical and/or readable sense to pass arguments and results.
@ -439,7 +487,7 @@ having introduced the return stack and the return mode, another world of possibi
in order to achieve this, uxn has an instruction called STH, stash.
this is the last instruction we had to cover in this tutorial series! :)
this is the last instruction of uxn that we had to cover in this tutorial series! :)
## STH
@ -468,7 +516,7 @@ a caret (^) after a value name indicates that it corresponds to a short.
```
@draw-horizontal-line ( x^ y^ length -- )
( beginning )
( ws: x^ y^ length / rs : )
( ws: x^ y^ length / rs: )
( store length in return stack )
STH ( ws: x^ y^ / rs: length )
@ -529,11 +577,11 @@ however, it shows how we can use these instructions to have a clean working stac
* have the subroutine draw a line made of sprites instead of single pixels
* modify the subroutine so that it can receive as an argument (in the working stack) the color of the sprites or pixels
* modify the subroutine so that it can receive as an argument (in the working stack) the address of the sprite to draw
* rewrite the subroutine so that it uses a short-sized length
* rewrite the subroutine so that it uses a short-sized length for the line, instead of a byte.
# the keep mode
the last element of uxntal that we can cover is its third mode for instructions: the keep mode.
the last basic element of uxntal that we have yet to cover is its third mode for instructions: the keep mode.
the keep mode is encoded in the 8th bit of an instruction byte, counting from right to left.
@ -576,7 +624,7 @@ actually, if you remember, on {uxn tutorial day 4} i shared with you a couple of
i said then that there was a more optimized set, and that we'd discuss it later.
now is that later moment!
now is that moment!
first of all, let's analyze what's happening with MOD. it is calculating what would be written in infix notation as follows, assumming that the slash (/) indicates an integer division
@ -586,9 +634,9 @@ a - ( a/b )*b
try for example replacing 'a' with 7 and 'b' with 3; the modulo or remainder should be 1
* a/b gives us the result of the integer division; 7/3 is 2
* (a/b)*b tries to get 'a' again, but possibly "fails" because of the remainder that was lost in the division; 2*3 is 6
* a - (a/b)*b calculates what's the difference between 'a' and the obtained result; 7-6 is 1
* a/b gives us the result of the integer division: 7/3 is 2
* (a/b)*b tries to get 'a' again, but possibly "fails" because of the remainder that was lost in the division: 2*3 is 6
* a - (a/b)*b calculates what's the difference between 'a' and the obtained result: 7-6 is 1
in our original macro, what happens goes as follows:
@ -602,9 +650,13 @@ SUB ( ws: 01 )
do you see a possibility for introducing the keep mode?
if you look carefully, you'll see that DUP2 is there in order to avoid losing the original values, so that we can perform the multiplication and subtraction later.
if you look carefully, you'll see that DUP2 is there in order to avoid losing the original values in the division, so that we can perform the multiplication and subtraction later.
therefore, DUP2 DIV is equivalent to... DIVk! a division that doesn't lose its operands!
but now, how can we perform the division without losing its operands and without using DUP2?
that's right!
DUP2 DIV is equivalent to... DIVk! a division that doesn't lose its operands!
```
#07 #03 ( ws: 07 03 )
@ -613,7 +665,7 @@ MUL ( ws: 07 06 )
SUB ( ws: 01 )
```
so our macro can have one byte less!
in this way, our macro can have one less byte!
we can generalize this behavior for the short mode, and obtain the optimal set of macros that i mentioned earlier:
@ -645,7 +697,6 @@ NEQk ( ws: length count flag / rs: )
,&loop JCN ( ws: length count / rs: )
```
## keep mode and the return stack
sometimes we'll want to stash a copy of a value that we'll use at the moment.
@ -674,7 +725,7 @@ STHkr ( retrieve a copy from the return stack )
new and interesting uses for the keep mode are still being found :)
don't hesitate to share them with us! and keep an eye over here for more possibilities!
don't hesitate to share them with us!
# more exercises
@ -692,7 +743,7 @@ maybe start with drawing only when a button is pressed. change color and/or "bru
you can have different selectable "modes": maybe they change the brush you are using, the way the brush behaves (e.g. in mirror? kaleidoscope?), and/or the shapes that are drawn.
consider how you would select those modes: on screen buttons? keys from the keyboard?
consider how you would select those modes: on screen buttons? keys from the keyboard? chords with mouse buttons?
keep in mind that you can change a device's vector during runtime: you could have a different on-mouse subroutine depending on the mode you have selected :)
@ -704,7 +755,7 @@ basically, the gates for interactive visual applications in the varvara computer
will you create games? small applications, useful or not? an instrument for live visuals? programs targeting specific handheld devices?
some things might appear difficult to build, but fortunately, (i feel-think) there's nothing else in the workings of the machine that we haven't covered already.
some things might appear difficult to build, but fortunately, as of now there's nothing else in the workings of the machine that we haven't covered already.
you can go slowly, step by step, practicing your stack wrangling and exercising the postfix brain, and you'll get anywhere you want :)

View File

@ -20,7 +20,7 @@ we will tackle the following elements in order:
in {uxn tutorial day 5} we discussed a way of creating a loop in order to repeat a 1bpp tile multiple times in a row.
here we will expand that procedure in order to have it repeated vertically in the whole screen.
here we will expand that procedure in order to have it also repeated vertically in the whole screen.
## setting up
@ -30,10 +30,9 @@ let's start with the following program as a template. it includes the data for a
( hello-pong.tal )
( devices )
|00 @System [ &vector $2 &pad $6 &r $2 &g $2 &b $2 ]
|20 @Screen [ &vector $2 &width $2 &height $2 &auto $1 &pad $1 &x $2 &y $2 &addr $2 &pixel $1 &sprite $1 ]
|80 @Controller [ &vector $2 &button $1 &key $1 ]
|90 @Mouse [ &vector $2 &x $2 &y $2 &state $1 &wheel $1 ]
|00 @System [ &vector $2 &pad $6 &r $2 &g $2 &b $2 ]
|20 @Screen [ &vector $2 &width $2 &height $2 &auto $1 &pad $1 &x $2 &y $2 &addr $2 &pixel $1 &sprite $1 ]
|80 @Controller [ &vector $2 &button $1 &key $1 ]
( macros )
%RTN { JMP2r }
@ -62,7 +61,7 @@ one way would be something like:
* add 8 (the size of the tile) to x
* is x less than the limit? if it is, repeat procedure, otherwise end
### a fist version
### a first version
let's say our initial x is 0000, our width is the screen width, and the tile we are drawing is tile-background.
@ -77,7 +76,7 @@ the first step, drawing the tile in x would be:
#03 .Screen/sprite DEO ( draw 1bpp sprite with color 3 and 0 )
```
adding 8 to x, we know already:
the second step, adding 8 to x, we know already:
```
.Screen/x DEI2 #0008 ADD2 ( add 8 to x )
@ -87,8 +86,8 @@ adding 8 to x, we know already:
checking if x is less than the limit, jumping if it is, would be something like:
```
.Screen/x DEI2
.Screen/width DEI2 ( the limit )
.Screen/x DEI2 ( get x )
.Screen/width DEI2 ( get the limit )
LTH2 ,&loop JCN ( jump if x is less than the limit )
```
@ -100,14 +99,17 @@ integrating all of it, we would be able to get:
&loop-x
#03 .Screen/sprite DEO ( draw 1bpp sprite with color 3 and 0 )
.Screen/x DEI2 #0008 ADD2 DUP2 ( add 8 to x )
.Screen/x DEI2 #0008 ADD2 ( add 8 to x )
DUP2 ( duplicate new x )
.Screen/x DEO2 ( store new x )
.Screen/width DEI2 ( the limit )
.Screen/width DEI2 ( get the limit )
LTH2 ,&loop-x JCN ( jump if x is less than the limit )
```
note the use of DUP2 in order to avoid re-reading the value of x.
this should work now! but let's discuss a nicer way of doing it :)
### a second version, using the stack
instead of reading the screen width and the x coordinate each time, we could use the stack to store and manipulate those values.
@ -120,7 +122,9 @@ after setting the tile address, we can push our limit (the screen width) and the
we will be using that value at the top of the stack as the x coordinate.
inside the loop, we can duplicate it to set it as the screen x, and to increment it. in between, we can send our sprite byte in order to draw the tile.
inside the loop, we can duplicate it to set it as the screen x, and to increment it.
in between, we can send our sprite byte in order to draw the tile.
```
&loop-x
@ -171,10 +175,9 @@ the following shows our program in context, completely filling the first row of
( hello-pong.tal )
( devices )
|00 @System [ &vector $2 &pad $6 &r $2 &g $2 &b $2 ]
|20 @Screen [ &vector $2 &width $2 &height $2 &auto $1 &pad $1 &x $2 &y $2 &addr $2 &pixel $1 &sprite $1 ]
|80 @Controller [ &vector $2 &button $1 &key $1 ]
|90 @Mouse [ &vector $2 &x $2 &y $2 &state $1 &wheel $1 ]
|00 @System [ &vector $2 &pad $6 &r $2 &g $2 &b $2 ]
|20 @Screen [ &vector $2 &width $2 &height $2 &auto $1 &pad $1 &x $2 &y $2 &addr $2 &pixel $1 &sprite $1 ]
|80 @Controller [ &vector $2 &button $1 &key $1 ]
( macros )
%RTN { JMP2r }
@ -233,7 +236,7 @@ WALL-MARGIN ( set initial y )
&loop-y
DUP2 .Screen/y DEO2 ( set y coordinate )
( draw row )
( - draw row here - )
#0008 ADD2 ( increment y )
@ -244,7 +247,7 @@ POP2 POP2 ( remove y and limit )
### nested loops
now that we have this structure, we can replace the "draw row" comment with our previous horizontal loop:
now that we have this structure, we can replace the "draw row here" comment with our previous horizontal loop:
```
;tile-background .Screen/addr DEO2 ( set tile address )
@ -320,7 +323,7 @@ nice!
in {uxn tutorial appendix a} you can find a detailed discussion of how to generalize a procedure like this one into a draw-tiles subroutine that draws an arbitrary rectangle filled with a given tile.
it goes into several possibilities for using uxntal in that abstract way: i'd say it's very interesting, but probably out of scope for making the game :)
it goes into several possibilities for using uxntal in that abstract way: i'd say it's very interesting, but it is definitely out of scope for making the game :)
# the paddles
@ -343,6 +346,7 @@ i used nasu to draw a paddle consisting of 2x3 tiles in 2bpp mode. i will number
2 3
4 5
```
=> https://100r.co/site/nasu.html 100R - nasu
the resulting data is the following:
@ -369,13 +373,13 @@ i drew the sprite using the blending mode 85 as indicated by nasu, but i will ch
let's build a subroutine that draws the 6 tiles of the paddle in the corresponding order.
we could have the subroutine receiving as argument the x and y position of its top left corner:
we could have the subroutine receiving as arguments the x and y position of its top left corner:
```
@draw-paddle ( x^ y^ -- )
```
but let's add the color byte as well:
but let's add a color byte for the sprite byte as well:
```
@draw-paddle ( x^ y^ color -- )
@ -500,7 +504,7 @@ let's reserve some space in the zero page for the x and y coordinates of each pa
we mentioned early that the x coordinate is constant; however, if we make it a variable then we can dinamically assign the x position of the paddles (especially the right one) depending on the size of the screen.
we can have a couple of macros to hold the dimensions of the paddles in order to use it later, and also its color:
we can have a couple of macros to hold the dimensions and color of the paddles in order to use them later:
```
%PADDLE-WIDTH { #0010 } ( 2 tiles )
@ -514,7 +518,7 @@ a margin to separate the paddles from the borders could be nice as well:
%MARGIN { #0010 }
```
finally, let's bring back our HALF2 macro:
finally, let's bring back our HALF2 macro from previous days:
```
%HALF2 { #01 SFT2 } ( short -- short/2 )
@ -522,7 +526,7 @@ finally, let's bring back our HALF2 macro:
### initialize positions
we can then initialize the positions of the paddles.
now we can initialize the positions of the paddles.
for the left x, we can just assign a constant value:
@ -559,7 +563,7 @@ in order to draw each paddle, we can do the following procedure inside our on-fr
### the program so far
omitting the definition of the subroutines, and as a way of having a checkpoint, right now our program would look like the following:
omitting the definition of the draw-background and draw-paddle subroutines, and as a way of having a checkpoint, right now our program would look like the following:
=> ./img/screenshot_uxn-pong-paddles.png screenshot of the two paddles, vertically centered and with the same margin relative to the sides
@ -570,7 +574,6 @@ omitting the definition of the subroutines, and as a way of having a checkpoint,
|00 @System [ &vector $2 &pad $6 &r $2 &g $2 &b $2 ]
|20 @Screen [ &vector $2 &width $2 &height $2 &auto $1 &pad $1 &x $2 &y $2 &addr $2 &pixel $1 &sprite $1 ]
|80 @Controller [ &vector $2 &button $1 &key $1 ]
|90 @Mouse [ &vector $2 &x $2 &y $2 &state $1 &wheel $1 ]
( macros )
%RTN { JMP2r }
@ -770,15 +773,15 @@ we can define a couple of macros to refer to its parameters:
### subroutine for drawing the ball
as we'll be drawing a single ball, we can embed in its drawing subroutine the use of its own zero-page variables for the coordinates, instead of getting them as arguments in the stack.
as we'll be drawing a single ball, we can write its drawing subroutine such that it takes its coordinates from the zero-page instead of getting them as arguments in the stack.
in our zero page we can define them:
in our zero page we can define the labels for the coordinates:
```
@ball [ &x $2 &y $2 ]
```
in our setup subroutine we can assign values to them, e.g. at the middle of the screen:
and then in our setup subroutine we can assign values to them, e.g. at the middle of the screen:
```
( inside setup )
@ -792,7 +795,7 @@ HALF2
.ball/y STZ2
```
and then we can use them in our subroutine.
the coordinates are ready, so now we can use them inside our subroutine.
let's have the subroutine receive the color as an argument, so that we can clear the ball like we do with the paddles:
@ -847,7 +850,7 @@ for the movement of the ball, we'll follow the same structure as before:
* update its position
* draw the ball in the new position
it would look something like the following, and could sit along the equivalent procedures for the paddles inside the on-frame subroutine:
it would look something like the following, and it could sit along the equivalent procedures for the paddles inside the on-frame subroutine:
```
( inside on-frame )
@ -867,11 +870,11 @@ now let's discuss how to build that update-ball subroutine :)
besides our variables for keeping track of the position of the ball, we should be able to keep track of the per-axis direction it is moving.
one approach could be to have a flag for each x and y, that indicate if we should increment or decrement them.
one approach could be to have a flag for each x and y that indicates if we should increment or decrement them.
another approach could be to have a speed variable for each x and y, that gets changed according to the direction we want the ball to go.
we will use this latter approach as it will help us discuss some perks of unsigned integer arithmetic!
we will use this latter approach with the speed as it will help us discuss some perks of unsigned integer arithmetic!
we include these variables in our zero page, complementing the x and y we had already:
@ -901,11 +904,15 @@ in order to move to the left, we might think that we should replace ADD2 with SU
but, to leave our code as it is now: is there a value of speed-x that will make x get smaller when adding them together?
in other contexts, one might say, "-1"! but we haven't used negative signs here in uxn; we can't.
in other contexts, one might say, "-1"!
but we haven't used negative signs here in uxn; we can't!
then, is there a positive value of speed-x that will make x get smaller when adding them together?
normally we could think there isn't, but here we are constrained by 8 or 16 bits. and what does that imply?
normally we would think there isn't and that the question doesn't make sense!
however, here we are constrained by 8 or 16 bits. and what does that imply?
for example, if we have the number ffff (16 bits, all are ones), and we add 0001, what do we get?
@ -916,11 +923,11 @@ for example, if we have the number ffff (16 bits, all are ones), and we add 0001
1 0000 0000 0000 0000
```
it makes sense, but the 1 at the left sits outside the 16 bits; in other contexts it would be called the carry bit.
ok, it's a bigger number, but the 1 at the left sits outside the 16 bits! in other contexts this would be called the carry bit.
in uxn, the result of adding ffff and 0001 is 0000: we would say we are overflowing the 16 bits.
in uxn, the result of adding ffff and 0001 is 0000: we say we are overflowing the 16 bits.
let's look at it the other way around: if we have 1, and we add ffff, we get 0, that is 1 less than 1.
let's look at it the other way around: if we have 0001, and we add ffff, we get 0000, that is 1 less than 1!
if we have 0002, and we add ffff:
@ -931,11 +938,11 @@ if we have 0002, and we add ffff:
1 0000 0000 0000 0001
```
we get 0001, that is 1 less than 2.
we get 0001, that is 1 less than 2!
in general, if we add ffff to a 16 bits number, we'll get a value that is 1 less than itself.
we can think then that ffff is like a "-1"!
therefore we can think that ffff is like a "-1"!
to get other "negative numbers", let's observe the following: if we subtract 1 from ffff, we get fffe. what happens if we add it to 2?
@ -950,7 +957,7 @@ we get 0! fffe works effectively as "-2"!
we could continue in that way getting more and more "negative" numbers that works thanks to the constrained size of computer memory.
going back to our code, if we initialize our speed with
going back to our code, if we initialize our speed with:
```
#ffff .ball/speed-x STZ2
@ -975,7 +982,7 @@ it might make sense to set these values as macros:
%BALL-NEGATIVE-SPEED { #ffff } ( -1 )
```
### implemeting the ball movement
### implementing the ball movement
based on what we just discussed, we can start our update-ball subroutine with the following:
@ -993,7 +1000,7 @@ based on what we just discussed, we can start our update-ball subroutine with th
RTN
```
if we complement our initilization with the speeds, we'll be able to see the ball moving:
if we complement our setup routine with the initial speeds, we'll be able to see the ball moving:
```
( inside setup )
@ -1008,6 +1015,8 @@ BALL-POSITIVE-SPEED .ball/speed-x STZ2
BALL-POSITIVE-SPEED .ball/speed-y STZ2
```
woohoo! it moves, but for the moment it flies away :)
## collisions with the walls
we have defined the general way of updating the position of the ball given its speed in x and y.
@ -1069,8 +1078,9 @@ the y coordinate of the bottom wall would be the height of the screen, less the
(inside update ball )
&check-bottom-wall
.ball/y LDZ2 BALL-SIZE ADD2 ( y + ball size )
.Screen/height DEI2 WALL-MARGIN SUB2 ( height - margin )
GTH2 ( is the ball y greater than the wall y? )
.Screen/height DEI2
WALL-MARGIN SUB2 ( height - margin )
GTH2 ( is the ball-y greater than the wall-y? )
,&set-negative-speed JCN
,&continue JMP
@ -1081,19 +1091,20 @@ the y coordinate of the bottom wall would be the height of the screen, less the
### update-ball code so far
our update-ball subroutine looks like the following right now:
```
@update-ball ( -- )
( update x )
( get speed-x and x )
.ball/speed-x LDZ2 .ball/x LDZ2 ( get x )
.ball/speed-x LDZ2 .ball/x LDZ2
ADD2 ( add them together )
.ball/x STZ2 ( store new x )
( update y )
( get speed-y and y )
.ball/speed-y LDZ2 .ball/y LDZ2 ( get y )
ADD2 ( add them together )
.ball/speed-y LDZ2 .ball/y LDZ2
ADD2 ( add them together )
.ball/y STZ2 ( store new y )
( check collisions with walls )
@ -1110,8 +1121,9 @@ our update-ball subroutine looks like the following right now:
&check-bottom-wall
.ball/y LDZ2 BALL-SIZE ADD2 ( y + ball size )
.Screen/height DEI2 WALL-MARGIN SUB2 ( height - margin )
GTH2
.Screen/height DEI2
WALL-MARGIN SUB2 ( height - margin )
GTH2 ( is the ball y greater than the wall y? )
,&set-negative-speed JCN
,&continue JMP
@ -1180,7 +1192,6 @@ and what happens if both conditions are not met at the same time?
we can let the ball keep moving, but checking that it hasn't crossed the left wall, by comparing with 0000.
the whole x-in-left code would end up looking like:
```
@ -1197,7 +1208,8 @@ the whole x-in-left code would end up looking like:
,&finish JCN
&reset-left
( here you can add a point to the right paddle )
( here you can increase the score of
the right paddle )
;reset JSR2
,&finish JMP
@ -1210,7 +1222,6 @@ the whole x-in-left code would end up looking like:
finish would be a label at the end of the subroutine, and reset is a subroutine that we will discuss later.
this approach of comparing with 0000 is the easiest, but keep in mind that it might not work if you change the ball speed: it could happen that it crosses the wall but with an x coordinate that is never equal to 0.
we can't really check if the x coordinate is less than 0, because as we discussed above, that would actually be a number close to ffff.
@ -1244,7 +1255,8 @@ for the right paddle we will do the same as above, but changing the comparisons
,&finish JCN
&reset-right
( here you can add a point to the left paddle )
( here you can increase the score
of the left paddle )
;reset JSR2
,&finish JMP
@ -1292,7 +1304,6 @@ here's all of the code we wrote today!
|20 @Screen [ &vector $2 &width $2 &height $2 &auto $1 &pad $1
&x $2 &y $2 &addr $2 &pixel $1 &sprite $1 ]
|80 @Controller [ &vector $2 &button $1 &key $1 ]
|90 @Mouse [ &vector $2 &x $2 &y $2 &state $1 &wheel $1 ]
( macros )
%RTN { JMP2r }
@ -1671,7 +1682,7 @@ RTN
&tile3 [ ff ff fe fe fc f8 f0 c0 06 06 0c 1c 38 f0 c0 00 ]
```
whew!
whew! :)
# more possibilities

View File

@ -13,28 +13,28 @@ the file device in the varvara computer allows us to read and write to external
its ports are normally defined as follows:
```
|a0 @File [ &vector $2 &success $2 &stat $2 &delete $1 &append $1 &name $2 &length $2 &load $2 &save $2 ]
|a0 @File [ &vector $2 &success $2 &stat $2 &delete $1 &append $1 &name $2 &length $2 &read $2 &write $2 ]
```
* the vector short is currently unused
* the success short stores the length of the data that was successfully read or written, or zero if there was an error
* the name short is for the memory address where the filename (null-terminated, i.e. with a 00) is stored
* the length short is the amount of bytes to read or write: don't forget that the program memory is ffff plus 1 bytes long, and that the program itself is stored there!
* the load short is for the starting memory address where the read data should be stored
* the save short is for the starting memory address where the data to be written is stored
* the read short is for the starting memory address where the read data should be stored
* the write short is for the starting memory address where the data to be written is stored
* the stat short is similar to load, but reads the directory entry for the filename
* the delete byte deletes the file when any value is written to it
* setting the append byte to 01 makes save append data to the end of the file, when it is the default, 00, save overwrites the contents from the start
* setting the append byte to 01 makes write append data to the end of the file. when the append byte has the the default value, 00, write overwrites the contents from the start
a read operation is started when the load short is written to, and a write operation is started when the save short is written to.
a read operation is started when the read short is written to, and a write operation is started when the write short is written to.
these might seem like a lot of fields, but we'll see that they are not too much of a problem!
these might seem like a lot of fields to handle, but we'll see that they are not too much of a problem!
## reading a file
in order to read a file, we need to know the following:
* the path of the file, written as a string of text in program memory: terminated by a 00, and with an appropriate label - this path would be relative to the location where uxnemu is ran.
* the path of the file, written as a labelled string of text in program memory and terminated by a 00 - this path would be relative to the location where uxnemu is ran.
* the amount of bytes that we want to read from the file: it's okay if this number is not equal to the size of the file; it can be smaller or even greater.
* the label for a reserved section of program memory where read data will be stored
@ -48,7 +48,7 @@ we can use a structure like the following, where the filename and reserved memor
#00ff .File/length DEO2 ( will attempt to read 255 bytes )
( set address for the data to read, and do read )
;file/data .File/load DEO2
;file/data .File/read DEO2
( check the success byte and jump accordingly )
.File/success DEI2 #0000 EQU2 ,&failed JCN
@ -68,7 +68,7 @@ RTN
note that for the filename we are using the raw string rune (") that allows us to write several characters in program memory until a whitespace is found.
int this example we are writing a character to the console according to the success short being zero or not, but we could decide to take any action that we consider appropriate.
in this example we are writing a character to the console according to the success short being zero or not, but we could decide to take any action that we consider appropriate.
also, in this example we are not really concerned with how many bytes were actually read: keep in mind that this information is stored in File/success until another read or write happens!
@ -80,7 +80,7 @@ not only we can choose to treat these bytes as text characters, but also we can
in order to write a file, we need:
* the path of the file
* the path of the file, written as a string in program memory as the case above
* the amount of bytes that we want to write into the file
* the label for the section of program memory that we will write into the file
@ -94,7 +94,7 @@ the following program will write "hello" and a newline (0a) into a file called "
#0006 .File/length DEO2 ( will attempt to write 6 bytes )
( set data starting address, and do write )
;file/data .File/save DEO2
;file/data .File/write DEO2
( read and evaluate success byte )
.File/success DEI2 #0006 NEQ2 ,&failed JCN
@ -114,13 +114,15 @@ RTN
note how similar it is to the load-file subroutine!
the only differences, beside the use of File/save instead of File/load, are the file length and the comparison for the success short: in this case we know for sure how many bytes should have been written.
the only differences, beside the use of File/write instead of File/read, are the file length and the comparison for the success short: in this case we know for sure how many bytes should have been written.
## a brief case study: the theme file
programs for the varvara computer written by 100r tend to have the ability to read a "theme" file that contains six bytes corresponding to the three shorts for the system colors.
this file has the name ".theme" and can be written to a local directory from nasu, using the ctrl+p shortcut.
these six bytes are in order: the first two are for the red channel, the next two for the green channel, and the last two for the blue channel.
this file has the name ".theme" and is written to a local directory from nasu whenever a spritesheet is saved.
=> https://wiki.xxiivv.com/site/theme.html uxn themes
@ -155,6 +157,8 @@ RTN
&r $2 &g $2 &b $2
```
note how the &data and &r labels are pointing to the same location: it's not a problem! :)
### writing the theme file
and for doing the opposite operation, we can read the system colors into our reserved space in memory, and then write them into the file:
@ -211,13 +215,17 @@ based on this, it should be straightforward for you to use them! e.g. in order t
.DateTime/hour DEI
```
## some time-based possibilities
i invite you to develop a creative visualization of time!
maybe you can use these values as coordinates for some sprites, or maybe you can use them as sizes or limits for shapes created with loops.
or what about conditionally drawing sprites, and/or changing the system colors depending on the time? :)
remember that for timing events with a little bit more precison, you can count the times that the screen vector has been fired!
you can also use the values of date and time as seeds to generate some pseudo-randomness!
lastly, remember that for timing events with more precision than seconds, you can count the times that the screen vector has been fired.
# the audio device
@ -236,13 +244,13 @@ similar to how in the screen device we can draw by pointing to addresses with sp
stretching the analogy: similar to how we can draw sprites in different positions on the screen, we can play our samples at different rates, volume, and envelopes.
we'll briefly discuss these concepts in order to use them, assuming that you might not be familiar with them.
we'll assume that you might not be familiar with these concepts, so we'll briefly discuss them.
## samples
as we mentioned above, we can think of the sample data as the equivalent of sprite data.
they have to be in program memory, they have a specific length, and we can refer to them by labels.
they have to be in program memory, they have a length that we have to know, and we can refer to them by labels.
the piano.tal example in the uxn repository, has several of them, all of them 256 bytes long:
@ -346,7 +354,9 @@ in the context of varvara, we can understand them as multiple unsigned bytes (u8
a "playhead" visits each of these numbers during a specific time, and uses them to set the amplitude of the sound wave.
the following images show the waveform of each one of these samples. when we loop them we get a tone based on that shape!
the following images show the waveform of each one of these samples.
when we loop these waveforms, we get a tone based on their shape!
piano-pcm:
=> ./img/screenshot_uxn-waveform_piano.png piano sample waveform
@ -363,7 +373,7 @@ tri-pcm:
saw-pcm:
=> ./img/screenshot_uxn-waveform_saw.png saw sample waveform
similar to how we have dealt with sprites and with the file device above, in order to set a sample in the audio device, we just have to write its address and its length:
similar to how we have dealt with sprites, and similar to the file device discussed above, in order to set a sample in the audio device we just have to write its address and its length:
```
;saw-pcm .Audio0/addr DEO2 ( set sample address )
@ -376,11 +386,13 @@ the frequency at which this sample is played (i.e. at which the wave amplitude t
the pitch byte makes the sample start playing whenever we write to it, similar to how the sprite byte performs the drawing of the sprite when we write to it.
the first 7 bits (from right to left) of the byte correspond to a midi note (and therefore to the frequency at which the sample will be played), and the eighth bit is a flag: when it's 0 the sample will be looped, and when it's 1 the sample will be played only once.
the first 7 bits (from right to left) of the byte correspond to a midi note, and therefore to the frequency at which the sample will be played.
the eighth bit is a flag: when it's 0 the sample will be looped, and when it's 1 the sample will be played only once.
normally we will want to loop the sample in order to generate a tone based on it. only when the sample is long enough it will make sense to not loop it and play it once.
regarding the midi note bits, it's a good idea to have a midi table around to see the hexadecimal values corresponding to different notes.
regarding the bits for the midi note, it's a good idea to have a midi table around to see the hexadecimal values corresponding to different notes.
=> https://wiki.xxiivv.com/site/midi.html midi table
@ -388,7 +400,7 @@ middle C (C4, or 3c in midi) is assumed to be the default pitch of the samples.
### a "sample" program
in theory, the following program should play our sample at that frequency, or not?
in theory, it would appear that the following program should play our sample at that frequency, or not?
```
( hello-sound.tal )
@ -442,15 +454,15 @@ in the ADSR short of the audio device, there is one nibble for each of the compo
the units for these durations are 15ths of a second.
as an example, if the duration of the attack component is 'f', then it will last one second (15/15 second, in decimal).
as an example, if the duration of the attack component is 'f', then it will last one second (15/15 of a second, in decimal).
the following will set the maximum duration on each of the components, making the sound last 4 seconds:
the following will set the maximum duration on each of the components, making the sound last 4 seconds in total:
```
#ffff .Audio0/adsr
```
ok, so now we are ready to play the sound!
ok, now we are ready to play the sound!
## playing the sample
@ -475,9 +487,11 @@ BRK
note (!) that it will play the sound only once, and it does it when the program starts.
i invite you to experiment with the ADSR values: how does the sound change when there's only one of them? or when all of them are small numbers? or with different combinations of durations?
### some suggested experiments
also, try changing the pitch byte: does it correspond with the midi values as expected?
i invite you to experiment modifying the ADSR values: how does the sound change when there's only one of them? or when all of them are small numbers? or with different combinations of durations?
also, try changing the pitch byte: does it correspond to your ears with the midi values as expected?
and how does the sound changes when you use a different sample? can you find or create different ones?
@ -485,7 +499,19 @@ and how does the sound changes when you use a different sample? can you find or
once we have set up our audio device with a sample, length, ADSR envelope and volume, we could play it again and again by (re)writing a pitch at a different moment; the other parameters can be left untouched.
keep in mind that every time you write a pitch, the playback of the sample and the shape of the envelope starts over.
for example, a macro like the following could allow us to play a note again according to the pitch given at the top of the stack:
```
%PLAY-NOTE { .Audio0/pitch DEO } ( pitch -- )
```
when a specific event happened, you could call it:
```
#3c PLAY-NOTE ( play middle C )
```
keep in mind that every time you write a pitch, the playback of the sample and the shape of the envelope starts over regardless of where it was.
### some ideas
@ -508,9 +534,15 @@ the output byte allows us to read the amplitude of the envelope. it returns 0 wh
## polyphony
the idea of having four audio devices is that we can have all of them play at once, and each one can have a different sample, ADSR envelope, volume, and pitch.
the idea of having four audio devices is that we can have all of them playing at once, and each one can have a different sample, ADSR envelope, volume, and pitch.
this gives us many more possibilities: maybe in a game there could be a melody playing in the background along with incidental sounds related to the gameplay? maybe you can build a sequencer where you can control the four devices as different tracks? or maybe you create a livecoding platform to have a dialog with each of the four instruments?
this gives us many more possibilities:
maybe in a game there could be a melody playing in the background along with incidental sounds related to the gameplay?
maybe you can build a sequencer where you can control the four devices as different tracks?
or maybe you create a livecoding platform to have a dialog with each of the four instruments?
in any case, don't hesitate to share what you create! :)