Compare commits

...

58 Commits

Author SHA1 Message Date
jota b3bcb6b4e2 Acentos añadidos al código y otras correciones breves 2022-01-15 10:04:37 -03:00
sejo 52d28de39d link al caracolito sin scheme 2022-01-14 16:57:49 -06:00
sejo d335d785d9 enlaces a /casa de ~jota 2022-01-14 16:55:55 -06:00
sejo 62731cd6aa quitar trabajo en proceso; corrección de encabezados de apéndices 2022-01-14 15:45:47 -06:00
sejo 471083a26d Merge branch 'main' of tildegit.org:sejo/compudanzas 2022-01-14 15:43:37 -06:00
sejo 9749e2e62b Merge pull request 'revisión_pausada_y_demases' (#7) from jota/compudanzas:revisión_pausada_y_demases into main
Reviewed-on: sejo/compudanzas#7
2022-01-14 21:42:34 +00:00
jota 9216e39cef Betabel en vez de remolacha. :) 2022-01-14 18:16:16 -03:00
jota f0b64c335a Más revisiones/correciones 2022-01-14 16:21:44 -03:00
sejo 4b762001a9 human computers and rustic computing links 2022-01-14 12:49:25 -06:00
jota b16a6c8045 Revisión pausada del Día 7 y Apéndice A, y correciones de otros Días. 2022-01-14 12:03:51 -03:00
sejo 94ac3a7c00 contador de 3 bits compuertas habituales 2022-01-13 18:42:14 -06:00
sejo a366da3859 added missing NIP instruction 2022-01-13 17:39:35 -06:00
jota bb7925f997 Revisión pausada del Día 5 y 6, y correciones breves de otros Días 2022-01-13 19:18:41 -03:00
jota cc10cd9624 Revision pausada del Día 3 y 4, y correciones breves de otros Días. 2022-01-13 12:21:44 -03:00
sejo 8c972f8398 enlace a octal en lugar de octales 2022-01-12 17:20:44 -06:00
sejo 3f2de48d7c Merge pull request 'traducción_día_7,_apéndice_a_y_demases' (#6) from jota/compudanzas:traducción_día_7_y_demases into main
Reviewed-on: sejo/compudanzas#6
2022-01-12 23:19:29 +00:00
jota 7a31edc11e Olvide guardar los cambios del Día 1 :) 2022-01-12 19:59:12 -03:00
jota 6517b7913d Revisión pausada del Día 1 y 2, adición de la parte en español en Hexadecimal y otras correciones breves. 2022-01-12 19:46:50 -03:00
jota 04273debea Traducción terminada del Día 7 y Apéndice A. Revisión breve de demases. 2022-01-12 15:33:35 -03:00
jota 2f7ea7474c Traducción del Día 7 y demases 2022-01-12 12:42:37 -03:00
sejo c0074c1b60 enlace a tutorial de uxn 2022-01-11 18:49:40 -06:00
sejo 089370325a cambio del enlace inicial 2022-01-11 18:46:24 -06:00
sejo 73630a15de corrección de enlaces a {tutorial de uxn} 2022-01-11 18:20:48 -06:00
sejo ea43a7f5e6 Merge pull request 'traducción_día_6_y_demases' (#5) from jota/compudanzas:traducción_día_6_y_ocb into main
Reviewed-on: sejo/compudanzas#5
2022-01-12 00:15:12 +00:00
jota d0a1719f05 Traducción terminada del Día 6, actualización del índice y documentos del Día 7 y Apéndice A añadidos. 2022-01-11 20:00:19 -03:00
jota 22323c4ab5 Traducción de macros en los demas días y revisiones del Día 6 2022-01-11 18:10:49 -03:00
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
43 changed files with 7116 additions and 303 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

@ -1,11 +1,17 @@
# hexadecimal
numeral system in base 16: it uses 16 digits, from 0 to 9 and from 'a' to 'f'.
eng: numeral system in base 16: it uses 16 digits, from 0 to 9 and from 'a' to 'f'.
there's a direct mapping between each possible combination of 4 bits (nibble), and an hexadecimal (hex) digit:
there's a direct mapping between each possible combination of 4 bits (nibble), and an hexadecimal (hex) digit:
//
esp: sistema numérico en base 16: utiliza 16 dígitos, del 0 al 9 y de la 'a' a la 'f'.
hay un mapeo directo entre cada posible combinación de 4 bits (nibble), y un dígito hexadecimal (hex):
+ <table>
+ <tr><th>binary</th><th>hex</th><th>dec</th></tr>
+ <tr><th>binar(y/io)</th><th>hex</th><th>dec</th></tr>
+ <tr><td>0000</td><td>0</td><td>0</td></tr>
+ <tr><td>0001</td><td>1</td><td>1</td></tr>
+ <tr><td>0010</td><td>2</td><td>2</td></tr>
@ -43,9 +49,16 @@ there's a direct mapping between each possible combination of 4 bits (nibble), a
# 24 bits
24 bits correspond to:
eng: 24 bits correspond to:
* 3 bytes
* 4 {base64} digits
* 6 hexadecimal digits
* 8 {octal} digits
esp: 24 bits corresponden a:
* 3 bytes
* 4 dígitos {base64}
* 6 dígitos hexadecimales
* 8 dígitos en {octal}

View File

@ -7,11 +7,13 @@ explorando computación a escala humana: ¿qué pasa cuando las computadoras son
# proyectos
=> ./introduction_to_uxn_programming_book.gmi {introduction to uxn programming book}
=> ./tutorial_de_uxn.gmi {tutorial de uxn}
=> ./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

@ -116,11 +116,41 @@ nor C5(x, p2,p3);
endmodule
```
## contador de 3 bits con compuertas habituales (11 o 14 participantes)
```
// contador de 3 bits con compuertas habituales
// entradas (3): a, b, c
// salidas (3): x, y, z
// compuertas (8): 3 de 1, 3 de 2, 2 de 3 entradas
module contador(a, b, c, x, y, z);
input wire a,b,c;
output wire x,y,z;
wire p1, p2, p3, p4, p5;
// negaciones
not C1(p1, a);
not C2(p2, b);
not C3(z, c);
// para y
xor C2(y, a,b);
// para x
and(p3, p1, b, c)
and(p4, a, p2)
and(p5, a, z)
or(x, p3, p4, p5)
endmodule
```
## contador de 3 bits con nor (13 o 16 participantes)
```
// contador de 3 bits con compuertas habituales
// contador de 3 bits con nor
// entradas (3): a, b, c
// salidas (3): x, y, z
// compuertas (10): 3 de 1, 4 de 2, 3 de 3 entradas

View File

@ -2,8 +2,10 @@
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}
~jota is translating {tutorial de uxn}
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

@ -53,6 +53,8 @@ texts by solderpunk in the {gemini} protocol
# non-electronic computers
=> https://wintermute.org/project/Rustic_Computing/ Brendan Howell - Rustic Computers
=> https://www.jeffreythompson.org/human-computers.php Jeff Thompson ++ Human Computers
=> https://xkcd.com/505 - a bunch of rocks, randall munroe, xkcd
=> http://www.blikstein.com/paulo/projects/project_water.html programmable water - paulo blikstein 2003
=> https://wiki.xxiivv.com/site/papier.html wdr papier computer

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

@ -1,14 +1,15 @@
# tutorial de uxn
una guía de inicio para programar la computadora varvara basada en el núcleo {uxn}, y un pausado complemento a la documentación oficial.
una guía introductoria y pausada para programar la computadora varvara basada en el núcleo {uxn}.
=> https://wiki.xxiivv.com/site/uxntal.html uxntal
=> https://wiki.xxiivv.com/site/uxnemu.html uxnemu
> El ecosistema de Uxn es un espacio de juego y exploración de computación personal, creado para desarrollar y utilizar pequeñas herramientas y juegos, y programable en su propio lenguaje ensamblador.
el tutorial está dividido en 8 días (o secciones), ya que puede ser seguido junto al taller.
=> https://100r.co/site/uxn.html 100R - uxn
(al día de hoy, este es un trabajo en proceso)
una traducción del {uxn tutorial} a cargo de ~jota.
=> https://texto-plano.xyz/~jota/ /casa de ~jota (web)
=> gemini://texto-plano.xyz/jota/ /casa de ~jota (gemini)
# día 1
@ -21,11 +22,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,7 +37,45 @@ también hablamos de instrucciones lógicas y de manipulación de la pila en uxn
# día 4
¡proximamente!
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
aquí hablamos de cómo podemos integrar todo lo que hemos cubierto para crear subrutinas y programas 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 sprites de varios tiles, y para comprobar las colisiones.
{tutorial de uxn día 6}
# día 7
aquí hablamos de los dispositivos del ordenador varvara que aún no hemos cubierto: audio, archivo y fechahora o "datetime".
este debería ser un final ligero y tranquilo de nuestro recorrido, ya que tiene que ver menos con la lógica de programación y más con las convenciones de entrada y salida en estos dispositivos.
{tutorial de uxn día 7}
# apéndices
## apéndice a: repetir un tile dentro de un rectángulo
aquí generalizaremos un procedimiento similar en una subrutina dibuja-tiles que dibuje un rectángulo relleno con un tile dado.
{tutorial de uxn apéndice a}
# índice tentativo
@ -129,6 +168,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

@ -0,0 +1,597 @@
# tutorial uxn apéndice a: repetir un tile dentro de un rectángulo
en la primera parte del {tutorial de uxn día 6} hablamos de cómo cubrir el fondo de la pantalla con un tile dado.
aquí generalizaremos un procedimiento similar en una subrutina dibuja-tiles que dibuje un rectángulo relleno con un tile dado. recibirá las coordenadas x,y de la esquina superior izquierda del rectángulo, su anchura y altura en píxeles, y la dirección del tile:
```
@dibuja-tiles ( x^ y^ ancho^ alto^ direc* -- )
```
un recordatorio de que estamos utilizando la convención de añadir un signo de intercalación (^) después del nombre de un valor para indicar que es un corto, y un asterisco (*) para indicar que es un corto que funciona como un puntero (es decir, una dirección en la memoria del programa)
vamos a detallar cómo llegar a dos versiones de esta subrutina, una que se basa en una fuerte manipulación de la pila, y otra que utiliza variables. esto con el fin de comparar ambos enfoques y darnos una visión más amplia de las posibilidades dentro de uxntal.
## configuración
comencemos con el siguiente programa como plantilla. incluye los datos para un sprite de 1bpp compuesto por líneas diagonales.
```
( hola-fondo.tal )
( dispositivos )
|00 @Sistema [ &vector $2 &pad $6 &r $2 &g $2 &b $2 ]
|20 @Pantalla [ &vector $2 &ancho $2 &alto $2 &auto $1 &pad $1 &x $2 &y $2 &direc $2 &píxel $1 &sprite $1 ]
( macros )
%RTN { JMP2r }
( programa principal )
|0100
@configuracion
( establecer los colores del sistema )
#2ce9 .Sistema/r DEO2
#01c0 .Sistema/g DEO2
#2ce5 .Sistema/b DEO2
BRK
@tile-fondo 1122 4488 1122 4488
```
## repetir un tile en una fila
¿qué procedimiento podríamos seguir para repetir el dibujo de un tile empezando por x, y terminando en un límite correspondiente a x+ancho?
una forma sería algo así como:
* dibujar tile en x
* añadir 8 (el tamaño del tile) a x
* ¿es x menor que el límite? si lo es, repite el procedimiento, si no, termina
### versión concreta
antes de abstraerlo, recomiendo que lo escribamos con números concretos.
digamos que nuestra x inicial es 0008, nuestro ancho es 0100, y el tile que estamos dibujando es tile-fondo.
el límite, x+ancho, sería 0108.
el primer paso, dibujar el tile en x sería:
```
;tile-fondo .Pantalla/direc DEO2 ( establecer la dirección del tile )
#0008 .Pantalla/x DEO2 ( establecer la x inicial )
#03 .Pantalla/sprite DEO ( dibujar sprite de 1bpp con color 3 y 0 )
```
añadiendo 8 a x, ya sabemos:
```
.Pantalla/x DEI2 #0008 ADD2 ( añadir 8 a x )
.Pantalla/x DEO2 ( guardar la nueva x )
```
comprobar si x es menor que el límite, saltando si lo es, sería algo así:
```
.Pantalla/x DEI2
#0108 ( el límite )
LTH2 ,&bucle JCN ( saltar si x es menor que el límite )
```
integrando todo ello, podríamos obtener:
```
;tile-fondo .Pantalla/direc DEO2 ( establecer la dirección del tile )
#0008 .Pantalla/x DEO2 ( establecer la x inicial )
&bucle-x
#03 .Pantalla/sprite DEO ( dibujar sprite de 1bpp con color 3 y 0 )
.Pantalla/x DEI2 #0008 ADD2 DUP2 ( añadir 8 a x )
.Pantalla/x DEO2 ( almacenar la nueva x )
#0108 LTH2 ,&bucle-x JCN ( salta si x es menor que el límite )
```
nótese el uso de DUP2 para evitar releer el valor de x.
### abstrayendo
ahora, digamos que queremos que el código anterior funcione con cualquier x inicial y ancho inicial dados, presentes en la pila antes de empezar.
podemos incluso pensar en ello como una subrutina por sí misma con la siguiente firma:
```
@dibuja-tiles-en-fila ( x^ ancho^ -- )
```
asumamos por el momento que la dirección del sprite ya fue establecida, para enfocarnos en `x` y en el ancho.
al iniciar la subrutina, el ancho está en la parte superior de la pila, seguido de la x inicial.
podemos utilizar estos dos valores para calcular el límite, que podemos almacenar en la pila de retorno.
una forma de conseguirlo, anotando el estado de la pila de trabajo después de cada instrucción, podría ser:
```
( estado inicial: ptra: x^ ancho^ )
OVR2 ( ptra: x^ ancho^ x^ )
ADD2 ( ptra: x^ límite^ )
STH2 ( ptra: x^ / pret: límite^ )
```
otra más:
```
( estado inicial: ptra: x^ ancho^ )
ADD2k ( ptra: x^ ancho^ límite^ )
STH2 ( ptra: x^ ancho^ / pret: límite^ )
POP2 ( ptra: x^ / pret: límite^ )
```
recuerda que estamos mostrando la parte superior de las pilas a su derecha.
después de estos pasos, la x inicial está en la parte superior de la pila, por lo que podemos enviarla directamente a la pantalla.
el último cambio que necesitaríamos es reemplazar nuestro límite codificado por una instrucción STH2kr (copiar el límite de la pila de retorno a la pila de trabajo), y terminar nuestra rutina con una POP2r (eliminar el límite de la pila de retorno).
nuestra subrutina se vería entonces de la siguiente manera:
```
@dibuja-tiles-en-fila ( x^ ancho^ -- )
( calcular y guardar el límite )
OVR2 ( ptra: x^ ancho^ x^ )
ADD2 ( ptra: x^ límite^ )
STH2 ( ptra: x^ / pret: límite^ )
.Pantalla/x DEO2 ( fijar x inicial )
&bucle-x
#03 .Pantalla/sprite DEO ( dibujar sprite con color 3 y 0 )
.Pantalla/x DEI2 #0008 ADD2 DUP2 ( añadir 8 a x )
.Pantalla/x DEO2 ( guardar la nueva x )
STH2kr ( copiar límite de pret en ptra )
LTH2 ,&bucle-x JCN ( saltar si x es menor que el límite )
POP2r ( hacer POP en límite de pret )
RTN
```
### programa completo
lo siguiente muestra nuestro programa en contexto, llenando completamente la primera fila de nuestra pantalla con nuestro tile:
=> ./img/screenshot_uxn-background-row.png captura de pantalla mostrando la primera fila de la pantalla varvara rellenada con líneas diagonales
```
( hola-fondo.tal )
( dispositivos )
|00 @Sistema [ &vector $2 &pad $6 &r $2 &g $2 &b $2 ]
|20 @Pantalla [ &vector $2 &ancho $2 &alto $2 &auto $1 &pad $1 &x $2 &y $2 &direc $2 &píxel $1 &sprite $1 ]
( macros )
%RTN { JMP2r }
( programa principal )
|0100
@configuracion
( establecer los colores del sistema )
#2ce9 .Sistema/r DEO2
#01c0 .Sistema/g DEO2
#2ce5 .Sistema/b DEO2
;tile-fondo .Pantalla/direc DEO2 ( establecer la dirección del tile )
#0000 ( x inicial )
.Pantalla/ancho DEI2 ( obtener el ancho de la pantalla )
;dibuja-tiles-en-fila JSR2
BRK
@dibuja-tiles-en-fila ( x^ ancho^ -- )
OVR2 ( ptra: x^ límite^ x^ )
ADD2 ( ptra: x^ límite^ )
STH2 ( ptra: x^ / pret: límite^ )
.Pantalla/x DEO2 ( fijar x inicial )
&bucle-x
#03 .Pantalla/sprite DEO ( dibujar sprite con color 3 y 0 )
.Pantalla/x DEI2 #0008 ADD2 DUP2 ( añadir 8 a x )
.Pantalla/x DEO2 ( guarda la nueva x )
STH2kr ( copiar límite de pret en ptra )
LTH2 ,&bucle-x JCN ( saltar si x es menor que el límite )
POP2r ( sacar el límite de pret )
RTN
@tile-fondo 1122 4488 1122 4488
```
## repetir una fila
similar a lo que acabamos de hacer: ¿cuál es el procedimiento que podríamos seguir para repetir verticalmente una fila empezando por `y`, y terminando en un límite correspondiente a y+altura?
siguiendo la misma estrategia, podríamos hacer:
* dibujar la fila en y
* añadir 8 (el tamaño del tile) a y
* ¿es `y` menor que el límite? si lo es, repetir el procedimiento, en caso contrario terminar
### versión concreta
utilicemos los mismos números que antes, suponiendo que nuestra `y` inicial es 0008, nuestra altura es 0100, y por tanto nuestro límite siendo 0108.
en el caso de x, comencemos en 0000 y tengamos un ancho correspondiente al ancho de la pantalla.
como la dirección no cambiaría en el proceso, podemos establecerla al principio y olvidarnos de ella.
el siguiente código se basa en el bucle anterior de x, pero ahora dibuja una fila en una coordenada `y` dada, le suma 8 y luego comprueba si es menor que el límite:
```
;tile-fondo .Pantalla/direc DEO2 ( establecer la dirección del tile )
#0008 .Pantalla/y ( establecer y inicial )
&bucle-y
( preparar y dibujar fila )
#0000 ( x inicial )
.Pantalla/ancho DEI2 ( obtener el ancho de la pantalla )
;dibuja-tiles-en-fila JSR2
.Pantalla/y DEI2 #0008 ADD2 DUP2 ( añade 8 a y )
.Pantalla/y DEO2 ( guardar la nueva y )
#0108 ( poner límite en la parte superior de la pila )
LTH2 ,&bucle-y JCN ( saltar si x es menor que el límite )
```
### versión abstracta
ahora, antes de saltar directamente a la emulación de la solución para dibujar la fila, vamos a notar que en este caso no es tan fácil.
¿por qué? porque la idea de nuestra subrutina dibuja-tiles es que debe ser capaz de recibir la x inicial y el ancho del rectángulo, y ahora mismo estos valores están codificados dentro del bucle.
esta debería ser la firma de nuestra subrutina:
```
@dibuja-tiles ( x^ y^ ancho^ alto^ direc* -- )
```
podemos abordar este problema bien con alguna "manipulación de la pila ", o bien con "variables ".
también hay que tener en cuenta que lo haremos porque estamos tratando de llegar a una subrutina generalizada.
si sólo quisiéramos cubrir toda la pantalla con un sprite, ya tenemos todo el código necesario: sólo tendríamos que adaptar el límite vertical del bucle para que se corresponda con la altura de la pantalla, ¡y ya está!
entonces podríamos pasar a la sección relativa de las paletas. sin embargo, lo que sigue puede ser interesante como una forma de ver un posible enfoque para escribir un código uxntal más complejo :)
### usando la manipulación de la pila
en principio podríamos simplemente manipular los elementos dados en la pila, almacenándolos cuando sea apropiado, para adaptar nuestra subrutina a su firma.
en primer lugar, la dirección del tile es el valor de la parte superior de la pila. podemos consumirlo y olvidarnos de él:
```
( ptra inicial: x^ y^ ancho^ alto^ direc* )
.Pantalla/direc DEO2 ( ptra: x^ y^ ancho^ alto^ )
```
pensando en el bucle vertical, necesitamos calcular su límite sumando la altura a `y`, y necesitamos establecer la `y` inicial.
podríamos hacer lo siguiente:
```
ROT2 ( ptra: x^ ancho^ altura^ y^ )
DUP2 ( ptra: x^ ancho^ altura^ y^ y^ )
( establecer y inicial: )
.Pantalla/y DEO2 ( ptra: x^ ancho^ alto^ y^ )
( calcular y almacenar el límite vertical )
ADD2 ( ptra: x^ ancho^ limite-y^ )
STH2 ( ptra: x^ ancho^ / pret: limite-y^ )
```
ahora, podríamos también almacenar el ancho y `x`, ya que las necesitamos después en su orden original (primero x, luego el ancho )
```
STH2 ( ptra: x^ / pret: limite-y^ ancho^ )
STH2 ( ptra: / pret: limite-y^ ancho^ x^ )
```
en teoría, la primera parte de nuestra subrutina podría verse como:
```
@dibuja-tiles ( x^ y^ ancho^ alto^ direc* -- )
( establecer la dirección del tile )
.Pantalla/direc DEO2 ( ptra: x^ y^ ancho^ alto^ )
ROT2 ( ptra: x^ ^ altura^ y^ )
DUP2 ( ptra: x^ ^ altura^ y^ )
( fijar `y` inicial )
.Pantalla/y DEO2 ( fijar `y` inicial, ptra: x^ ^ altura^ y^ )
( calcular y guardar limite-y )
ADD2 ( ptra: x^ ^ limite-y^ )
STH2 ( ptra: x^ ^ / pret: limite-y^ )
( almacenar ancho y `x` )
STH2 STH2 ( ptra: / pret: limite-y^ ancho^ x^ )
&bucle-y
( preparar y dibujar la fila )
( recuperar x )
STH2r ( ptra: x^ / pret: limite-y^ ancho^ )
( recuperar el ancho )
STH2r ( ptra: x^ ancho^ / pret: limite-y^ )
;dibuja-tiles-en-fila JSR2
```
el problema es que dentro del bucle, ambas instrucciones STH2r recuperan y consumen los valores de `x` y ancho de la pila de retorno. por tanto, en la siguiente iteración no podríamos volver a utilizarlos, ya que se perderían.
podemos pensar que podríamos reemplazar estas instrucciones con STH2kr:
```
&bucle-y
( preparar y dibujar la fila )
( recuperar x )
STH2kr ( ptra: x^ / pret: limite-y^ ancho^ x^ )
```
¡pero entonces no podemos recuperar el ancho porque la x sigue en la parte superior de la pila de retorno!
oh, muchas dificultades, pero por el bien del ejemplo de la pila, vamos a seguir resolviendo esto (?)
¿cómo podemos poner el ancho en la parte superior de la pila de retorno? tal vez con un intercambio aplicado a la pila de retorno:
```
SWP2r ( ptra: x^ / pret: limite-y^ x^ ancho^ )
```
entonces podemos recuperar el ancho y utilizarlo:
```
STH2kr ( ptra: x^ ancho^ / pret: limite-y^ x^ ancho^ )
;dibuja-tiles-en-fila JSR2 ( ptra: / pret: limite-y^ x^ ancho^ )
```
¿qué sigue? añadir 8 a `y`, y comprobar si es menor que el límite. la primera parte va sin problemas:
```
.Pantalla/y DEI2 #0008 ADD2 DUP2 ( añade 8 a `y`; ptra: y^ y^ / pret: limite-y^ x^ ancho^ )
.Pantalla/y DEO2 ( almacenar nueva `y`; ptra: y^ / pret: limite-y^ x^ ancho^ )
```
para conseguir el límite en la pila de trabajo para la comparación, tenemos que girar la pila de retorno:
```
ROT2r ( ptra: y^ / pret: x^ ancho^ limite-y^ )
STH2kr ( ptra: y^ limite-y^ / pret: x^ ancho^ limite-y^ )
```
pero ah, antes de hacer la comparación y el salto, debemos reordenar la pila de retorno para que se corresponda con la ordenación que teníamos al principio del bucle:
```
SWP2r ( ptra: y^ limite-y^ / pret: x^ limite-y^ ancho^ )
ROT2r ( ptra: y^ limite-y^ / pret: limite-y^ ancho^ x^ )
```
ahora podemos hacer la comparación y saltar:
```
LTH2 ,&bucle-y JCN ( salta si x es menor que el límite )
```
después debemos limpiar la pila de retorno:
```
POP2r POP2r POP2r
```
después de todo esto, nuestra subrutina tendría el siguiente aspecto:
```
@dibuja-tiles ( x^ y^ ancho^ alto^ direc* -- )
( establecer la dirección del tile )
.Pantalla/direc DEO2 ( ptra: x^ y^ ancho^ alto^ )
ROT2 ( ptra: x^ ^ altura^ y^ )
DUP2 ( ptra: x^ ^ altura^ y^ )
( fijar `y` inicial )
.Pantalla/y DEO2 ( fijar `y` inicial, ptra: x^ ^ altura^ y^ )
( calcular y almacenar limite-y )
ADD2 ( ptra: x^ ^ limite-y^ )
STH2 ( ptra: x^ ^ / pret: limite-y^ )
( almacenar ancho y `x` )
STH2 STH2 ( ptra: / pret: limite-y^ ancho^ x^ )
&bucle-y
( preparar y dibujar la fila )
( recuperar x )
STH2kr ( ptra: x^ / pret: limite-y^ ancho^ x^ )
( recuperar el ancho )
SWP2r ( ptra: x^ / pret: limite-y^ x^ ancho^ )
STH2kr ( ptra: x^ ancho^ / pret: limite-y^ x^ ancho^ )
;dibuja-tiles-en-fila JSR2 ( ptra: / pret: limite-y^ x^ ancho^ )
.Pantalla/y DEI2 #0008 ADD2 DUP2 ( añadir 8 a y )
.Pantalla/y DEO2 ( almacenar la nueva y )
( recuperar limite-y )
ROT2r ( ptra: y^ / pret: x^ ^ limite-y^ )
STH2kr ( ptra: y^ limite-y^ / pret: x^ ancho^ limite-y^ )
( reordenar la pila de retorno )
SWP2r ( ptra: y^ limite-y^ / pret: x^ limite-y^ ancho^ )
ROT2r ( ptra: y^ limite-y^ / pret: limite-y^ ancho^ x^ )
LTH2 ,&bucle-y JCN ( salta si x es menor que el límite )
POP2r POP2r POP2r ( limpiar la pila de retorno )
RTN
```
podemos entonces llamarlo de la siguiente manera para obtener un cuadrado de 256x256 lleno de tiles:
```
#0008 #0008 ( `x` y `y` )
#0100 #0100 ( ancho y alto )
;tile-fondo
;dibuja-tiles JSR2
```
=> ./img/screenshot_uxn-background-square.png captura de pantalla que muestra un gran cuadrado en la pantalla varvara compuesto por líneas diagonales
### usando variables
comparemos el enfoque anterior con el uso de variables relativas.
iremos "con todo" de una manera relativamente derrochadora, sin optimizar los procedimientos que podrían beneficiarse de la manipulación de la pila.
declararemos las siguientes etiquetas para nuestras variables, después del RTN que termina la subrutina:
```
( variables )
&alto $2 &ancho $2 &y $2 &x $2 &limite-y $2
```
ahora, iniciamos la subrutina de la misma manera que antes, estableciendo la dirección para nuestro sprite:
```
( ptra inicial: x^ y^ ancho^ alto^ direc* )
.Pantalla/direc DEO2 ( ptra: x^ y^ ancho^ alto^ )
```
entonces, simplemente almacenamos los siguientes valores en direcciones relativas:
```
,&alto STR2
,&ancho STR2
&y STR2
&x STR2
```
nótese que vamos en orden inverso.
después de estas operaciones las pilas están vacías.
entonces podemos fijar la `y` inicial y calcular el límite vertical, utilizando los valores almacenados en las variables:
```
( establecer `y` inicial )
,&y LDR2 DUP2 ( ptra: y^ y^ )
.Pantalla/y DEO2 ( ptra: y^ )
( calcular limite-y )
,&alto LDR2 ( ptra: y^ alto^ )
ADD2 ( ptra: limite-y^ )
,&limite-y STR2 ( ptra: )
```
nuestro bucle ahora tendría el siguiente aspecto:
```
&bucle-y
( recuperar `x` y ancho )
,&x LDR2
&ancho LDR2
( dibujar fila )
;dibuja-tiles-en-fila JSR2
.Pantalla/y DEI2 #0008 ADD2 DUP2 ( añade 8 a y )
.Pantalla/y DEO2 ( almacenar la nueva y )
( recuperar el límite vertical )
,&limite-y LDR2
LTH2 ,&bucle-y JCN ( saltar si x es menor que el límite )
```
¡y eso es todo!
compara esto con la versión "concreta" que desarrollamos anteriormente, ¡es muy similar en su estructura!
la subrutina completa tendría el siguiente aspecto:
```
@dibuja-tiles ( x^ y^ ancho^ alto^ direc* -- )
( establecer la dirección del tile )
.Pantalla/direc DEO2 ( ptra: x^ y^ ancho^ alto^ )
( almacenar valores )
,&altura STR2
,&ancho STR2
&y STR2
,&x STR2
( establecer `y` inicial )
,&y LDR2 DUP2 ( ptra: y^ y^ )
.Pantalla/y DEO2 ( ptra: y^ )
( calcular el límite vertical )
,&alto LDR2 ( ptra: y^ alto^ )
ADD2 ( ptra: limite-y^ )
,&limite-y STR2 ( ptra: )
&bucle-y
( recuperar `x` y ancho )
,&x LDR2
,&ancho LDR2
( dibujar fila )
;dibuja-tiles-en-fila JSR2
.Pantalla/y DEI2 #0008 ADD2 DUP2 ( añade 8 a y )
.Pantalla/y DEO2 ( almacenar la nueva y )
( recuperar el límite vertical )
,&limite-y LDR2
LTH2 ,&bucle-y JCN ( saltar si x es menor que el límite )
RTN
( variables )
&alto $2 &ancho $2 &y $2 &x $2 &limite-y $2
```
como he dicho antes, podemos encontrar aquí algunas oportunidades de optimización.
tal vez el límite vertical puede ser escondido en la pila de retorno como en el bucle dibuja-tiles-en-fila, o tal vez la variable para la altura y para la `y` inicial no son necesarios.
dejo que lo resuelvas :)
nota que esta subrutina tal como está requiere 24 bytes de memoria de programa más que la versión de la pila.
en nuestro caso eso no es un gran problema, pero es una buena manera de evaluar nuestras prioridades: código súper legible pero probablemente ineficiente (como esta última subrutina), código súper optimizado pero probablemente ilegible (código de sólo escritura, dicen), o algo en el medio.
## dibuja-fondo
ahora que tenemos estas bonitas subrutinas, podemos simplemente envolverlas en otra que cubrirá todo el fondo con nuestro tile elegido.
por ejemplo:
```
@dibuja-fondo ( -- )
#0000 #0010 ( x e `y` iniciales )
.Pantalla/ancho DEI2
.Pantalla/alto DEI2 #0010 SUB2
;tile-fondo
;dibuja-tiles JSR2
RTN
```
que podemos llamar simplemente desde nuestra subrutina de iniciación:
```
;dibuja-fondo JSR2
;dibuja-fondo JSR2
```
=> ./img/screenshot_uxn-background-full.png captura de pantalla mostrando la pantalla varvara cubierta de líneas diagonales.
¡bonito! te recomiendo que te tomes un pequeño descanso; ¡esto ha sido pesado!
# apoyo
si te ha gustado este tutorial y te ha resultado útil, considera compartirlo y darle tu {apoyo} :)

View File

@ -1,6 +1,6 @@
# tutorial uxn: día 1, aspectos básicos
# tutorial uxn: día 1, aspectos básicos
hola! en esta primera sección del {tutorial de uxn} vamos a hablar de los aspectos básicos de la computadora uxn llamada varvara, su paradigma de programación en un lenguaje llamado uxntal, su arquitectura, y por que puede que quieras aprender a programarla.
¡hola! en esta primera sección del {tutorial de uxn} vamos a hablar de los aspectos básicos de la computadora uxn llamada varvara, su paradigma de programación en un lenguaje llamado uxntal, su arquitectura, y por que puede que quieras aprender a programarla.
también saltaremos directo a nuestros primeros programas simples para demostrar conceptos fundamentales que desarrollaremos en los próximos días.
@ -8,29 +8,29 @@ también saltaremos directo a nuestros primeros programas simples para demostrar
o primero que nada... ¿qué es uxn?
> Uxn es una computadora portable de 8 bit capaz de correr herramientas y juegos simples programable en su propio pequeño lenguaje ensamblador. Es también un ámbito de juego donde aprender habilidades básicas de computación.
=> https://wiki.xxiivv.com/site/uxn.html XXIIVV - uxn
te invito a leer "why create a smol virtual computer" del sitio de 100R, también:
> El ecosistema de Uxn es un espacio de juego y exploración de computación personal, creado para desarrollar y utilizar pequeñas herramientas y juegos, y programable en su propio lenguaje ensamblador.
=> https://100r.co/site/uxn.html 100R - uxn
uxn es el núcleo de la computadora virtual (¿por el momento?) varvara. es lo suficientemente simple como para ser emulada por diversas plataformas de computación viejas y nuevas y ser seguida a mano.
+te invito a leer "why create a smol virtual computer" ("porqué crear una pequeña computadora virtual") del sitio de 100R, para que conozcas más sobre la historia del proyecto.
uxn es el núcleo de la computadora virtual varvara. es lo suficientemente simple como para ser emulada por diversas plataformas de computación viejas y nuevas y ser seguida a mano.
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
* ámbito de práctica y experimentación de computación dentro de límites
* ya portada a plataformas de computación actuales y de varios años de antigüedad
¡todos estos conceotos suenan genial para mí, y espero que para tí también! sin embargo, noto algunos aspectos que pueden hacerla parecer no tan asequible:
¡todos estos conceptos suenan genial para mí, y espero que para tí también!
sin embargo, noto algunos aspectos que pueden hacerla parecer no tan asequible:
* es programada en un lenguaje ensamblador, uxntal
* utiliza notación {postfix} (alias notación polaca inversa) / está inspirada en máquinas forth
* utiliza notación {postfix} (alias notación polaca inversa) y está inspirada en máquinas forth
la idea de este tutorial es explorar estos dos aspectos y revelar cómo trabajan juntos para dar a uxn su poder con una complejidad relativamente baja.
@ -38,9 +38,9 @@ la idea de este tutorial es explorar estos dos aspectos y revelar cómo trabajan
el núcleo uxn está inspirado por las máquinas forth en que utiliza la recombinación de componentes simples para lograr soluciones apropiadas, y en que es una máquina basada en pila.
esto implica que está principalmente basada en interacciones con una pila "push down", dónde las operaciines son indicadas mediante la llamada notación postfija.
esto implica que está principalmente basada en interacciones con una pila "push down", dónde las operaciones son indicadas mediante la llamada notación postfija.
> Notación Polaca Reversa (NPR), también conocida como notación postfija polaca o simplemente notación postfija, es una notación matemática en la que los operadores siguen a sus operandos [...]
> Notación Polaca Inversa (NPR), también conocida como notación postfija polaca o simplemente notación postfija, es una notación matemática en la que los operadores siguen a sus operandos [...]
=> https://es.wikipedia.org/wiki/Notaci%C3%B3n_polaca_inversa Notación Polaca Inversa - Wikipedia
@ -58,9 +58,9 @@ dónde, leyendo de izquierda a derecha:
* el número 48 es empujado en la pila
* + toma dos elementos de la parte superior de la pila, los suma, y empuja el resultado en la pila
el libro Starting Forth tiene algunas ilustraciones grandiosas sobre este proceso de suma:
el libro Starting Forth tiene algunas buenas ilustraciones sobre este proceso de suma:
=> https://www.forth.com/starting-forth/1-forth-stacks-dictionary/#The_Stack_Forth8217s_Workspace_for_Arithmetic The Stack: Forths Workspace for Arithmetic
=> https://www.forth.com/starting-forth/1-forth-stacks-dictionary/#The_Stack_Forth8217s_Workspace_for_Arithmetic The Stack: Forths Workspace for Arithmetic (Inglés)
## de infija a postfija
@ -109,13 +109,13 @@ las palabras binarias de 8 bits, también conocidas como bytes, son los elemento
uxn puede manejar también palabras binarias de 16 bits (2 bytes), también conocidas como cortos, mediante la concatenación de dos bytes consecutivos. vamos a hablar más sobre esto en el segundo día de este tutorial.
los números en uxn son expresados utilizando el sistema hexadecimal (base 16), en el qhe cada dígito (nibble) va de 0 a 9 y luego de 'a' a 'f' (en minúscula).
los números en uxn son expresados utilizando el sistema hexadecimal (base 16), en el que cada dígito (nibble) va de 0 a 9 y luego de 'a' a 'f' (en minúscula).
un byte necesita dos dígitos hexadecimales (nibbles) para ser expresado, y un corto necesita cuatro.
## el cpu uxn
se ha dicho que el cpu uxn es una remolacha, capaz de ejecutar 32 instrucciones diferentes con tres banderas de modo diferentes.
se ha dicho que el cpu uxn es una betabel, capaz de ejecutar 32 instrucciones diferentes con tres banderas de modo diferentes.
cada instrucción junto con sus banderas de modo puede ser codificada en una sola palabra de 8 bits.
@ -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 :)
@ -188,13 +192,14 @@ $ cd uxn
$ ./build.sh
```
si todo fué bién, verás varios mensajes en la terminal y una pequeña ventana con el título uxn, y una aplicación demo: uxnemu está ahora corriendo una "rom" correspondiendo a esa aplicación.
si todo fue bién, verás varios mensajes en la terminal y una pequeña ventana con el título uxn, y una aplicación demo: uxnemu está ahora corriendo una "rom" correspondiendo a esa aplicación.
## controles uxnemu
* 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
@ -304,7 +315,7 @@ en la segunda linea ocurren varias cosas:
* |0100 : puede que recuerdes este número de antes - este es el valor inicial del contador de programa; la dirección del primer byte que el cpu lee. usamos esta notación para indicar que lo que esté escrito después será escrito en memoria después de esta dirección.
* LIT : esta aparece dos veces, y es una instrucción uxn con las siguientes acciones: empuja el siguiente valor en memoria a la pila, y hace que el contador de programa saltee ese byte.
* 68 : un número hexadecimal, que corresponde al código ascii del caracter 'h'
* 68 : un número hexadecimal, que corresponde al código ascii del carácter 'h'
* 18 : un número hexadecimal, que corresponde a una dirección de entrada/salida: dispositivo 1 (consola), dirección 8.
* DEO : otra instrucción uxn, que podemos definir como lo siguiente: escribir el byte dado en la dirección de dispositivo dada, ambos tomados de la pila ( byte dirección -- )
@ -320,16 +331,22 @@ 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!
¡nuestro programa está enviando el valor hexadecimal 68 (carácter 'h') a la salida estandard!
puedes ver los valores hexadecimales de los caracteres ascii en la siguiente tabla:
puedes ver los valores hexadecimales de los carácteres ascii en la siguiente tabla:
=> 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:
```
@ -366,7 +382,7 @@ puedes encontrar los opcodes de todas las 32 instrucciones en la referencia uxnt
## programa hola
podemos expandir nuestro programa para imprimir más caracteres:
podemos expandir nuestro programa para imprimir más carácteres:
```
( hola.tal )
@ -374,20 +390,18 @@ 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
las runas son caracteres especiales que indican a uxnasm algún pre procesamiento a hacer al ensamblar nuestro programa.
las runas son carácteres especiales que indican a uxnasm algún pre procesamiento a hacer al ensamblar nuestro programa.
## runa de pad absoluto
@ -399,9 +413,9 @@ 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.
éste carácter define un "hex literal": es básicamente un atajo para la instrucción LIT.
usando esta runa, podemos reescribir nuestro primer programa como:
@ -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,13 +431,17 @@ 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 :)
ésta es la runa de caracter raw: '
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.
nos permite que uxnasm decodifique el valor numérico de un caracter ascii.
## runa de carácter "crudo" o "raw"
ésta es la runa de carácter raw: '
nos permite que uxnasm decodifique el valor numérico de un carácter ascii.
nuestro "programa hola" luciría de la siguiente manera, usando las nuevas runas que acabamos de aprender:
@ -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,44 +499,44 @@ 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.
podemos definir un macro llamado EMIT que tomará de la pila un byte correspondiente a un carácter, y lo imprimirá en la salida estandard. para esto, necesitamos la runa %, y llaves para la definición.
¡no olvides los espacios!
```
( escribe un caracter a la salida estandard )
%EMIT { .Console/write DEO } ( caracter -- )
( escribe un carácter a la salida estandard )
%EMIT { .Consola/escribe DEO } ( carácter -- )
```
para llamar a un macro, sólo escribimos su nombre:
```
( imprime caracter h )
( imprime carácter h )
LIT 'h EMIT
```
@ -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 -- )
( imprime un carácter en la salida estandard )
%EMIT { .Consola/escribe DEO } ( carácter -- )
( imprime una nueva línea )
%NL { #0a EMIT } ( -- )
@ -554,15 +572,15 @@ usando todos estos macros y runas, nuestro programa puede terminar luciendo como
termina siendo ensamblado en los mismos 25 bytes que los ejemplos de arriba, pero con suerte más legible y mantenible.
podemos "mejorar" este programa haciendo que un loop imprima los caracteres, pero estudiaremos eso más tarde :
podemos "mejorar" este programa haciendo que un loop imprima los carácteres, pero estudiaremos eso más tarde :
# ejercicios
## reubicando EMIT
en nuestro programa previo, el macro EMIT es llamado justo después de empujar un caracter a la pila.
en nuestro programa previo, el macro EMIT es llamado justo después de empujar un carácter 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 carácteres y luego los emita (o haga "EMIT") todos con una secuencia como esta?
```
EMIT EMIT EMIT EMIT EMIT
@ -572,10 +590,16 @@ EMIT EMIT EMIT EMIT EMIT
si miras en la tabla ascii, verás que el código hexadecimal 30 corresponde al dígito 0, 31 al dígito 1, siguiendo hasta el 39 que corresponde al dígito 9.
define un macro IMPRIMIR-DIGITO que toma un número (del 0 al 9) de la pila, e imprime el correspondiente dígito en la salida estandard.
define un macro IMPRIMIR-DÍGITO que toma un número (del 0 al 9) de la pila, e imprime el correspondiente dígito en la salida estandard.
```
%IMPRIMIR-DIGITO { } ( número -- )
%IMPRIMIR-DÍGITO { } ( 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-DÍGITO
```
# instrucciones del día 1
@ -598,4 +622,3 @@ en el {tutorial de uxn día 2} empezamos a explorar los aspectos visuales de la
# apoyo
si este tutorial te ha resultado de ayuda, considera compartirlo y brindarle tu {apoyo} :)

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

View File

@ -0,0 +1,962 @@
# tutorial uxn: día 4, variables y bucle de animación
¡esta es la cuarta sección del {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 manejos complejos 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 &píxel $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!
## 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 una frecuencia de 60 veces por segundo: podemos utilizar la subrutina bajo en-cuadro para cambiar el contenido de la pantalla, generando animación, y/o también podemos utilizarla para otros propósitos relacionados con la temporización.
## 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 &píxel $1 &sprite $1 ]
( init )
|0100
( establecer los 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 el vector de pantalla )
;en-cuadro .Pantalla/vector DEO2
BRK
@en-cuadro ( -> )
( dibujar un píxel en el fondo con el color 1 )
#01 .Pantalla/píxel 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 píxel.
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:
```
@píxel-x 0008
@píxel-y 0008
```
o si no quisiéramos iniciarlas aquí, podríamos definirlas de la siguiente manera:
```
@píxel-x $2
@píxel-y $2
```
recuerda que $2 crea un pad relativo de dos bytes: esto hace que píxel-y sea una etiqueta para una dirección en memoria dos bytes después de píxel-x. y cualquier código posterior ocurrirá dos bytes después de píxel-y.
también podríamos usar etiquetas y sub-etiquetas, de manera muy similar a como definimos los dispositivos y sus puertos:
```
@píxel [ &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: "load", carga y empuja hacia abajo en la pila el valor en la dirección absoluta dada ( dirección -- valor )
* STA: "store", 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 píxel/x, los incrementaría en uno, y los almacenaría de nuevo en píxel/x:
```
;píxel/x LDA2 ( cargar píxel/x en la pila )
INC2 ( incrementar )
;píxel/x STA2 ( almacenar el resultado en píxel/x )
BRK
@píxel [ &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 píxel/x para enviarlo a Pantalla/x:
```
;píxel/x LDA2 ( cargar píxel/x en la pila )
INC2 ( incrementar )
DUP2 ( duplicar el resultado )
;pantalla/x DEO2 ( establecer como pantalla/x )
;píxel/x STA2 ( y guardar el resultado en píxel/x )
BRK
@píxel [ &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:
```
@píxel [ &x 0008 &y 0008 ]
```
estos contenidos iniciales cambiarán cada vez que usemos una instrucción STA allí :)
## variables en la página cero
las variables con dirección absoluta funcionan bien para los casos en los que queremos poder acceder a su contenido desde cualquier parte de nuestro programa (es decir, "variables globales").
sin embargo, uxn tiene un mecanismo mejor para esos casos: ¡la página cero!
como recordarás, la página cero consiste en las primeras 256 direcciones de la memoria del programa. normalmente, un programa comienza en la dirección 0100, que es la siguiente dirección después de la página cero.
podemos referirnos a cualquiera de las 256 direcciones de la página cero utilizando un solo byte, en lugar de los dos bytes que se necesitan para las direcciones absolutas.
algo importante a tener en cuenta es que el contenido de la página cero no está presente en las roms uxn.
esto significa que una salvedad de usar variables allí, es que para iniciarlas necesitamos hacerlo durante el tiempo de ejecución, almacenando valores de la pila en ellas.
### etiquetas en la página cero
las etiquetas para la página cero funcionarían igual que antes; sólo tenemos que especificar que están en la página cero con una almohadilla absoluta:
```
|0000 ( página cero )
@píxel [ &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: "load", carga y empuja hacia abajo en la pila el valor en la dirección de la página cero dada ( dirección -- valor )
* STZ: "store", 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 &píxel $1 &sprite $1 ]
( página cero )
|0000
@píxel [ &x $2 &y $2 ]
( init )
|0100
( establecer los colores del sistema )
#2ce9 .Sistema/r DEO2
#01c0 .Sistema/g DEO2
#2ce5 .Sistema/b DEO2
( establecer coordenadas iniciales x,y )
#0008 .píxel/x STZ2
#0008 .píxel/y STZ2
( establecer el 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 )
.píxel/x LDZ2 .Pantalla/x DEO2
.píxel/y LDZ2 .Pantalla/y DEO2
( dibujar un píxel en el fondo con color 1 )
#01 .Pantalla/píxel DEO
( incrementar el píxel/x )
.píxel/x LDZ2 INC2 .píxel/x STZ2
BRK
```
notemos el uso de la runa literal de dirección de página cero (.) para referirse a la etiqueta .píxel.
además, observe que en el caso de .píxel la dirección se refiere a la página cero, a la que se accede con LDZ/STZ, y en el caso de .Pantalla la dirección se refiere al espacio de direcciones entrada/salida (i/o), al que se accede con DEI/DEO.
### un poco de práctica de manipulación en la pila
nota que las siguientes instrucciones también incrementarían .píxel/x, pero estableciendo su dirección sólo una vez:
```
.píxel/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 corto desde la página cero )
%PC-INC2 { DUP LDZ2 INC2 ROT STZ2 } ( pc-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 desfases ("offsets") relativos y pueden considerarse positivas y negativas, 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: "load", carga y empuja hacia abajo en la pila el valor en la dirección relativa dada ( dirección -- valor )
* STR: "store", 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 )
,píxel/x LDR2 .Pantalla/x DEO2
,píxel/y LDR2 .Pantalla/y DEO2
( dibujar un píxel en el fondo con el color 1 )
#01 .Pantalla/píxel DEO
( incrementa píxel/x )
,píxel/x LDR2 INC2 ,píxel/x STR2
BRK
@píxel [ &x $2 &y $2 ]
```
nótese el uso de la runa coma (,) para indicar que es una dirección relativa; uxnasm calcula el desfase requerido asumiendo que será utilizado en la siguiente instrucción.
en este caso realmente no podemos duplicar ese desfase como hicimos anteriormente con la dirección de página cero, porque es específica de la posición en el código en que fue escrita.
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 )
,&píxel-x LDR2 .Pantalla/x DEO2
,&píxel-y LDR2 .Pantalla/y DEO2
( dibujar un píxel en el fondo con el color 1 )
#01 .Pantalla/píxel DEO
( incrementa píxel/x )
,&píxel-x LDR2 INC2 ,&píxel-x STR2
BRK
( variables locales de en-cuadro )
&píxel-x $2 &píxel-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 píxel 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/píxel, con un byte apropiado para definir su color y orientación, y tendríamos que establecer la dirección de los datos de nuestro sprite en Pantalla/direc.
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 &píxel $1 &sprite $1 ]
( macros/constantes )
%MITAD2 { #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 los 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 MITAD2 #0004 SUB2 .Pantalla/y DEO2
( fijar la dirección del sprite )
;cuadrado .Pantalla/direc DEO2
( establecer el 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 soltados. esto puede ser muy útil para algunas aplicaciones.
pero, ¿cómo podemos tratar de hacer una acción continua cuando una tecla se mantiene presionada?
en algunos sistemas operativos, si mantenemos una tecla pulsada, ésta dispara el vector controlador varias veces, ¡pero no necesariamente al mismo ritmo que el vector pantalla!
¡esta repetición puede no permitir un movimiento suave como el que podemos conseguir si comprobamos el estado del controlador dentro de la subrutina en-cuadro!
### cuadrado horizontalmente interactivo
el siguiente programa nos permite controlar la posición horizontal de nuestro cuadrado mediante las teclas de dirección.
=> ./img/screencap_uxn-moving-square.gif animación que muestra un cuadrado moviéndose horizontalmente en la pantalla, aparentemente controlado por un humano.
¡nótese las similitudes entre el programa anterior, y lo que cubrimos en el {tutorial de uxn día 3}!
```
( hola-sprite-en-movimiento.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 &píxel $1 &sprite $1 ]
|80 @Controlador [ &vector $2 &botón $1 &tecla $1 ]
( macros/constantes )
%MITAD2 { #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 los 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 MITAD2 #0004 SUB2 .Pantalla/y DEO2
( fijar la dirección del sprite )
;cuadrado .Pantalla/direc DEO2
( establecer el 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/botón DEI
#40 AND ( aislar el bit 6, correspondiente a la izquierda )
,&izquierda JCN ( saltar si no es 0 )
.Controlador/botón 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/fotogramas y animarlos ejecutándolos en secuencia!
## los fotogramas
para 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 00 00 01 03
&fotograma1 00 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 00
&fotograma7 c0 80 00 00 00 00 00 00
```
nótese que cada fotograma consta de 8 bytes. eso implica que hay un desfase 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 desfase 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.
```
( 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 día 3}, en particular la discusión de las operaciones lógicas.
## 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 mencionamos, el siguiente fotograma (fotograma1) comienza 8 bytes después.
la subetiqueta para cada fotograma siguiente está 8 bytes después de la anterior.
o, otra forma de verlo:
* el fotograma0 esta 0 bytes después de la etiqueta de animación
* el fotograma1 esta 8 bytes después de la etiqueta de animación
* el fotograma2 esta 16 bytes después de la etiqueta de animación
* el fotograma3 esta 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 :)
la cantidad de bytes que separa cada subetiqueta se llama "offset" o desfase.
### calculando el desfase
después de aplicar el módulo 8 a nuestro cuentafotogramas podemos multiplicarlo por 8 para obtener el desfase 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 desfase )
```
### 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 desfase en un corto 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 desfase )
A-CORTO ( convertir a corto )
```
otra forma, menos clara pero bastante divertida (y algo más corta en memoria de programa), consistiría en empujar el 00 antes de que ocurra cualquier otra cosa:
```
#00 ( empujar el byte alto del desfase )
.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 desfase )
```
### añadiendo el desfase
añadir este desfase 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 desfase )
A-CORTO ( convertir a corto )
; n ( obtener la dirección de la animación )
ADD2 ( añadir el desfase 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 píxelado. la diagonal se mueve desde abajo a la derecha hasta arriba a la izquierda
```
( hola-animación.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 &píxel $1 &sprite $1 ]
( macros/constantes )
%MITAD2 { #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
( establecer los colores del sistema )
#2ce9 .Sistema/r DEO2
#01c0 .Sistema/g DEO2
#2ce5 .Sistema/b DEO2
( fijar Pantalla/x e `y` a la mitad de la pantalla, menos 4 )
.Pantalla/ancho DEI2 MITAD2 #0004 SUB2 .Pantalla/x DEO2
.Pantalla/alto DEI2 MITAD2 #0004 SUB2 .Pantalla/y DEO2
( establecer la dirección del sprite )
;animación .Pantalla/direc DEO2
( establecer el 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-borrar .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 desfase )
A-CORTO ( convertir a corto )
;animación ( obtener la dirección de la animación )
ADD2 ( añadir el desfase 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 )
@animación
&fotograma0 00 00 00 00 00 00 01 03
&fotograma1 00 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 00
&fotograma7 c0 80 00 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 posibilidad divertidas, ¡te invito a dibujar el tile varias veces en diferentes lugares y posiblemente con diferentes modos de rotación! ¡eso puede generar animaciones más interesantes!
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 fotogramas 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 desfase )
A-CORTO ( convertir a corto )
;animación ( obtener la dirección de la animación )
ADD2 ( añadir el desfase 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 algunas fallas ("glitches") aproximadamente cada 4 segundos: esto se debe a que el cuentafotograma se sobrepasa y no da una buena secuencia de resultados para esos divisores.
esto también puede ocurrir si tienes una animación que consta de un número de fotogramas que no es una potencia de 2, y utilizas una operación MOD normal para calcular el desfase al fotograma correspondiente.
la solución más sencilla para estos problemas sería utilizar un número de fotogramas de pequeño tamaño que sólo causara esos fallos de sobreflujo aproximadamente cada 18 minutos.
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 mantener.
¡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 mantener.
¡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 @Ratón [ &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 bit al segundo botón del ratón, 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 presiona ninguno de los botones
* 01 cuando sólo se presiona el primer botón
* 02 cuando sólo se presiona el segundo botón
* 04 cuando sólo se presiona el tercer botón
nota que al igual que el dispositivo controlador, este sistema nos permite comprobar si hay varios botones presionados a la vez:
* 03 cuando el primer y el segundo botón están presionados
* 05 cuando se presionan el primer y el tercer botón
* 06 cuando se presionan el segundo y el tercer botón
* 07 cuando se presionan los tres botones
recuerda que podemos utilizar las máscaras AND, tal y como se introdujo en el {tutorial de uxn día 3}, para aislar y evaluar por separado cualquiera de estos bits.
## cortos 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 presionado 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 (presionado o no presionado)
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 presiona 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-ratón.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 &píxel $1 &sprite $1 ]
|90 @Ratón [ &vector $2 &x $2 &y $2 &estado $1 &pad $3 &despx $2 &despy $2 ]
( init )
|0100
( establecer los colores del sistema )
#2ce9 .Sistema/r DEO2
#01c0 .Sistema/g DEO2
#2ce5 .Sistema/b DEO2
( establecer vector del ratón )
;en-ratón .Ratón/vector DEO2
( establecer dirección del sprite )
;cuadrado .Pantalla/direc DEO2
BRK
@en-ratón ( -> )
.Ratón/x DEI2 .Pantalla/x DEO2
.Ratón/y DEI2 .Pantalla/y DEO2
( salta si se presiona algún botón )
.Ratón/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 &píxel $1 &sprite $1 ]
|90 @Ratón [ &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 los colores del sistema )
#2ce9 .Sistema/r DEO2
#01c0 .Sistema/g DEO2
#2ce5 .Sistema/b DEO2
( establecer el vector del ratón )
;en-ratón .Ratón/vector DEO2
( establecer dirección del sprite )
;puntero_icn .Pantalla/direc DEO2
BRK
@en-ratón ( -> )
( 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 )
.Ratón/x DEI2 .puntero/x STZ2
.Ratón/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 el {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-ratón 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 c vnm a la posición correspondiente en la subrutina en-ratón?
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 )
.Ratón/x DEI2 .puntero/x STZ2
.Ratón/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 )
.Ratón/x DEI2 DUP2 .puntero/x STZ2 .Pantalla/x DEO2
.Ratón/y DEI2 DUP2 .puntero/y STZ2 .Pantalla/y DEO2
```
esto dejaría nuestra subrutina en-ratón vacía:
```
@en-ratón( -> )
BRK
```
### usando saltos normales
con lo que ya sabemos, y dependiendo de la posición de dibuja-puntero con respecto a en-ratón, podríamos hacer un salto relativo:
```
@en-ratón ( -> )
,dibuja-puntero JMP
&retorno
( algo más aquí )
BRK
```
o un salto absoluto:
```
@en-ratón ( -> )
;dibuja-puntero JMP2
&retorno
( algo más aquí )
BRK
```
el detalle esta en que si queremos que ocurra algo más después de dibujar el puntero dentro de en-ratón, no podemos atrás fácilmente.
al final de nuestra subrutina dibuja-puntero, necesitaríamos "saltar hacia atrás" así:
```
;en-ratón/retornar 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-ratón ( -> )
,dibuja-puntero JSR
( algo más aquí )
BRK
```
## salto absoluto
y en el caso de un salto absoluto:
```
@en-ratón ( -> )
;dibuja-puntero JSR2
( algo más aquí )
BRK
```
## retornando
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 a 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 corto, 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 cortos, necesitamos activar el modo corto también:
```
JMP2r ( saltar a la dirección absoluta en la parte superior de la pila de retorno )
```
en muchos programas uxntal verás esta instrucción escrita como una macro, RTN ( retornar o "return" ):
```
%RTN { JMP2r }
```
podemos terminar una subrutina usando esta macro para "retornar" 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 &píxel $1 &sprite $1 ]
|90 @Ratón [ &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 los colores del sistema )
#2ce9 .Sistema/r DEO2
#01c0 .Sistema/g DEO2
#2ce5 .Sistema/b DEO2
( establecer el vector del ratón )
;en-ratón .Ratón/vector DEO2
( establecer dirección del sprite )
;puntero_icn .Pantalla/direc DEO2
BRK
@en-ratón ( -> )
;dibuja-puntero JSR2 ( o ,dibuja-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 )
.Ratón/x DEI2 .puntero/x STZ2
.Ratón/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, "stash" o almacenar.
¡esta es la última instrucción de uxn que teníamos en mente cubrir en este tutorial! :)
## 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 corto.
```
@dibuja-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 píxel con color 2 )
#02 .Pantalla/píxel 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 MITAD2 ( empujar y )
#ff ( empujar longitud de la línea )
;dibuja-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 pequeño tamaño 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 8vo 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 dibuja-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-ratón 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 de mano 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 uxntal 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

@ -0,0 +1,570 @@
# tutorial uxn: día 7, más dispositivos
esta es la séptima y última sección del {tutorial de uxn}! aquí hablamos de los dispositivos del ordenador varvara que aún no hemos cubierto: audio, archivo y fechahora o "datetime".
este debería ser un final ligero y tranquilo de nuestro recorrido, ya que tiene que ver menos con la lógica de programación y más con las convenciones de entrada y salida en estos dispositivos.
¡comencemos!
# el dispositivo de archivo
el dispositivo de archivo en el ordenador varvara nos permite leer y escribir en archivos externos.
sus puertos se definen normalmente de la siguiente manera:
```
|a0 @Archivo [ &vector $2 &éxito $2 &estad $2 &borrar $1 &adjuntar $1 &nombre $2 &largo $2 &leer $2 &escribir $2 ]
```
* el corto vector no se utiliza actualmente
* el corto éxito almacena la longitud de los datos que se han leído o escrito con éxito, o cero si ha habido un error
* el corto nombre es para la dirección de memoria donde se almacena el nombre del archivo (terminado en cero, es decir, con un 00)
* el corto largo es la cantidad de bytes a leer o escribir: ¡no olvidemos que la memoria del programa es ffff más 1 byte de largo, y que el programa mismo se almacena allí!
* el corto leer es para la dirección de memoria inicial donde los datos de lectura deben ser almacenados
* el corto escribir es para la dirección de memoria inicial donde se almacenan los datos a escribir
* el corto estad es similar al de leer, pero lee la entrada del directorio para el nombre del archivo
* el byte borrar borra el archivo cuando se escribe cualquier valor en él.
* establecer el byte adjuntar a 01 hace que `escribir` añada datos al final del archivo. cuando el byte adjuntar tiene el valor por defecto, 00, `escribir` sobrescribe el contenido desde el principio
una operación de lectura se inicia cuando se escribe en el corto `leer`, y una operación de escritura se inicia cuando se escribe en el corto `escribir`.
¡estos pueden parecer muchos detalles para manejar, pero veremos que no son demasiado problema!
## leer un archivo
para leer un archivo, necesitamos saber lo siguiente:
* la ruta del archivo, escrita como una cadena de texto etiquetada en la memoria del programa y terminada por un 00 - esta ruta sería relativa a la ubicación donde se ejecuta uxnemu.
* la cantidad de bytes que queremos leer del archivo: no pasa nada si este número no es igual al tamaño del archivo; puede ser menor o incluso mayor.
* la etiqueta para una sección reservada de la memoria del programa donde se almacenarán los datos leídos
¡y eso es todo!
podemos usar una estructura como la siguiente, donde el nombre del archivo y la memoria reservada están bajo una etiqueta, y la subrutina carga-archivo bajo otra:
```
@carga-archivo ( -- )
;archivo/nombre .Archivo/nombre DEO2 ( dirección de la ruta del archivo )
#00ff .Archivo/largo DEO2 ( intentará leer 255 bytes )
( establecer la dirección de los datos a leer y hacer lectura )
;archivo/datos .Archivo/lectura DEO2
( comprobar el byte éxito y saltar según corresponda )
Archivo/éxito DEI2 #0000 EQU2 ,&fallo JCN
&éxito
LIT 'Y .Consola/escribir DEO
RTN
&fallo
LIT 'N .Consola/escritura DEO
RTN
@archivo
&nombre "prueba.txt 00
&datos $ff ( reservando 255 bytes para los datos )
```
nótese que para el nombre del archivo estamos usando la runa de cadena cruda o `raw` (") que nos permite escribir varios caracteres en la memoria del programa hasta encontrar un espacio en blanco.
en este ejemplo estamos escribiendo un carácter en la consola en función de que el corto `éxito` sea cero o no, pero podríamos decidir realizar cualquier acción que consideremos apropiada.
además, en este ejemplo no nos preocupa realmente cuántos bytes se han leído realmente: ¡tenga en cuenta que esta información se almacena en Archivo/éxito hasta que se produzca otra lectura o escritura!
es importante recordar que, como siempre en este contexto, estamos tratando con bytes crudos.
¡no sólo podemos elegir tratar estos bytes como caracteres de texto, sino que también podemos elegir usarlos como sprites, coordenadas, dimensiones, colores, etc!
## escribir un archivo
para escribir un archivo, necesitamos:
* la ruta del archivo, escrita como una cadena en la memoria del programa como el caso anterior
* la cantidad de bytes que queremos escribir en el archivo
* la etiqueta de la sección de la memoria del programa que escribiremos en el archivo
¡tenga en cuenta que el archivo se sobrescribirá completamente a menos que `adjuntar` se establezca a 01!
el siguiente programa escribirá "hola" y una nueva línea (0a) en un archivo llamado "prueba.txt":
```
@guardar-archivo ( -- )
;archivo/nombre .Archivo/nombre DEO2 ( establecer el nombre de archivo )
#0006 .Archivo/largo DEO2 ( intentará escribir 6 bytes )
( establecer la dirección de inicio de los datos, y hacer la escritura )
;archivo/datos .Archivo/escribir DEO2
( leer y evaluar el byte éxito )
.Archivo/éxito DEI2 #0006 NEQ2 ,&fallo JCN
&éxito
LIT 'Y .Consola/escribir DEO
RTN
&fallo
LIT 'N .Consola/escribir DEO
RTN
@archivo
&nombre "prueba.txt 00
&datos "hola 0a
```
¡observe lo similar que es a la subrutina cargar-archivo!
las únicas diferencias, además del uso de Archivo/escribir en lugar de Archivo/leer, son la longitud del archivo y la comparación para el corto éxito: en este caso sabemos con seguridad cuántos bytes deberían haberse escrito.
## un breve estudio de caso: el archivo de temas
los programas para el ordenador varvara escritos por 100r suelen tener la capacidad de leer un archivo "tema" que contiene seis bytes correspondientes a los tres cortos para los colores del sistema.
estos seis bytes están en orden: los dos primeros son para el canal rojo, los dos siguientes para el canal verde y los dos últimos para el canal azul.
este archivo tiene el nombre de ".theme" y se escribe en un directorio local de nasu cada vez que se guarda una hoja de sprites.
=> https://wiki.xxiivv.com/site/theme.html temas uxn
### leyendo el archivo de temas
podríamos adaptar nuestra subrutina anterior para cargar el archivo de temas y aplicar sus datos como colores del sistema:
```
@cargar-tema ( -- )
;tema/nombre .Archivo/nombre DEO2 ( establecer la dirección de la ruta del archivo )
#0006 .Archivo/largo DEO2 ( intentará leer 6 bytes )
( establecer la dirección de los datos a leer y hacer lectura )
;tema/datos .Archivo/cargar DEO2
( comprobar el byte éxito y saltar según corresponda )
.Archivo/éxito DEI2 #0006 NEQ2 ,&fallo JCN
&éxito
( establecer los colores del sistema a partir de los datos leídos )
;tema/r LDA2 .Sistema/r DEO2
;tema/g LDA2 .Sistema/g DEO2
;tema/b LDA2 .Sistema/b DEO2
RTN
&fallo
RTN
@tema
&nombre ".theme 00
&datos ( reservando 6 bytes para los datos: )
&r $2 &g $2 &b $2
```
observe cómo las etiquetas &datos y &r apuntan a la misma ubicación: ¡no es un problema! :)
### escribiendo el archivo de temas
y para hacer la operación contraria, podemos leer los colores del sistema en nuestro espacio reservado en memoria, y luego escribirlos en el archivo:
```
@guardar-tema ( -- )
( leer los colores del sistema en la memoria del programa )
.Sistema/r DEO2 ;tema/r STA2
.Sistema/g DEO2 ;tema/g STA2
.Sistema/b DEO2 ;tema/b STA2
;tema/nombre .Archivo/nombre DEO2 ( establecer la dirección de la ruta del archivo )
#0006 .Archivo/leer DEO2 ( intentará escribir 6 bytes )
( establecer la dirección de los datos y hacer la escritura )
;tema/datos .Archivo/guardar DEO2
( comprobar el byte de éxito y saltar según corresponda )
.Archivo/éxito DEI2 #0006 NEQ2 ,&fallo JCN
&éxito
( ¿informar el éxito? )
RTN
&fallo
RTN
```
¡te invito a comparar estas subrutinas con las presentes en los programas 100r como nasu!
=> https://git.sr.ht/~rabbits/nasu/tree/main/item/src/main.tal código fuente de nasu
# el dispositivo fechahora
el dispositivo fechahora (o "datetime") puede ser útil para el cronometraje de baja precisión y/o para las visualizaciones del tiempo.
tiene varios campos que podemos leer, todos ellos basados en la hora del sistema actual y la zona horaria:
```
|b0 @FechaHora [ &año $2 &mes $1 &día $1 &hora $1 &minuto $1 &segundo $1 &ddls $1 &dda $2 &eshdv $1 ]
```
* el byte año corresponde al número de año de la llamada era común
* el byte mes cuenta los meses desde enero (es decir, enero es 0, febrero 1, etc.)
* el byte día cuenta los días del mes a partir del 1
* los bytes hora, minuto y segundo corresponden a lo que se espera: sus valores van de 0 a 23, o de 0 a 59 respectivamente
* ddls (día de la semana) es un byte que cuenta los días desde el domingo (es decir, el domingo es 0, el lunes es 1, el martes es 2, etc.)
* dda (día del año) es un byte que cuenta los días desde el 1 de enero (es decir, el 1 de enero es 0, el 2 de enero es 1, etc.)
* eshdv (es horario de verano) es una bandera, 01 si es horario de verano y 00 si no lo es.
basándonos en esto, debería ser sencillo utilizarlos. por ejemplo, para leer la hora del día en la pila, haríamos:
```
.FechaHora/hora DEI
```
## algunas posibilidades basadas en el tiempo
¡te invito a desarrollar una visualización creativa del tiempo!
tal vez puedas usar estos valores como coordenadas para algunos sprites, o tal vez puedas usarlos como tamaños o límites para figuras creadas con bucles.
o ¿qué tal dibujar sprites condicionalmente, y/o cambiar los colores del sistema dependiendo de la hora? :)
¡también puedes utilizar los valores de la fecha y la hora como semillas para generar algo de pseudo-aleatoriedad!
por último, recuerda que para cronometrar eventos con más precisión que segundos puedes contar las veces que se ha disparado el vector pantalla.
# el dispositivo de audio
por fin, ¡el dispositivo de audio! o debería decir, ¡los dispositivos de audio!
varvara tiene cuatro dispositivos estéreo idénticos (o "canales"), que se mezclan antes de pasar a los altavoces/auriculares:
```
|30 @Audio0 [ &vector $2 &posición $2 &salida $1 &pad $3 &adsr $2 &largo $2 &direc $2 &volumen $1 &tono $1 ]
|40 @Audio1 [ &vector $2 &posición $2 &salida $1 &pad $3 &adsr $2 &largo $2 &direc $2 &volumen $1 &tono $1 ]
|50 @Audio2 [ &vector $2 &posición $2 &salida $1 &pad $3 &adsr $2 &largo $2 &direc $2 &volumen $1 &tono $1 ]
|60 @Audio3 [ &vector $2 &posición $2 &salida $1 &pad $3 &adsr $2 &largo $2 &direc $2 &volumen $1 &tono $1 ]
```
de forma similar a como en el dispositivo de pantalla podemos dibujar apuntando a direcciones con datos de sprite, en los dispositivos de audio podremos reproducir sonidos apuntando a direcciones con datos de audio (muestras o "samples").
extendiendo la analogía: de forma similar a como podemos dibujar sprites en diferentes posiciones en la pantalla, podemos reproducir nuestras muestras a diferentes velocidades, volumen y envolventes.
supondremos que no estás familiarizado con estos conceptos, así que los discutiremos brevemente.
## muestras
como hemos mencionado anteriormente, podemos pensar en los datos de las muestras como el equivalente a los datos de los sprites.
tienen que estar en la memoria del programa, tienen una longitud que debemos conocer, y podemos referirnos a ellos mediante etiquetas.
el ejemplo piano.tal en el repositorio uxn, tiene varios de ellos, todos de 256 bytes de largo:
```
@piano-pcm
8182 8588 8d91 959b a1a6 aaad b2b5 b8bd
c1c7 cbd0 d5d9 dde1 e5e5 e4e4 e1dc d7d1
cbc5 bfb8 b2ac a6a2 9c97 928d 8884 807c
7977 7574 7372 7272 7273 7372 706d 6964
605b 5650 4d49 4643 4342 4244 4548 4a4d
5052 5556 5758 5554 5150 4c4a 4744 423f
3d3c 3a38 3835 3431 3030 2f31 3336 393e
4449 4e54 5a60 666b 7175 7b82 8990 989e
a6ab b1b6 babd bebf bfbe bbb9 b6b3 b0ae
aaa8 a6a3 a19e 9c9a 9997 9696 9798 9b9e
a1a4 a6a9 a9ac adad adae aeaf b0b0 b1b1
b3b3 b4b4 b4b3 b3b1 b0ad abab a9a9 a8a8
a7a5 a19d 9891 8b84 7e77 726e 6b6b 6b6c
6f71 7477 7776 7370 6c65 5e56 4e48 423f
3d3c 3b3a 3a39 3838 3839 393a 3c3e 4146
4a50 575b 6064 686a 6e70 7274 7677 7a7d
@violín-pcm
8186 8d94 9ba0 a3a7 acb1 b5bc c2c7 cacc
cecf d0d1 d3d5 d8db dee1 e3e5 e6e5 e5e3
dfdc d7d0 c8c2 bbb2 a99f 968c 847c 746e
675f 5851 4b43 3e3a 3533 312e 2c2b 2826
2422 2122 2327 2d34 3c44 4c57 5f68 7075
7b80 8487 8789 8a8c 8d90 9397 999c 9ea0
a2a2 a2a0 9c97 9491 8f8e 908f 918f 8e88
827a 726a 6058 5047 423f 3f40 4245 4748
4949 4746 4545 4a4f 5863 717f 8b9a a6b1
b8be c1c1 bfbd bab5 b1af acac aeb1 b7bc
c2c9 cfd3 d5d4 d3d3 d1ce cbc6 c0ba b3ab
a39a 8f85 7b72 6c67 6462 605f 5e5d 5b58
5550 4d49 4848 4949 4a4d 5052 5558 5b5e
6164 686c 7074 7677 7979 7a7b 7b7a 7977
7473 6f6e 6b69 696b 6f72 7576 7574 716b
655d 554e 4742 3f3f 4045 4b52 5a62 6b74
@sin-pcm
8083 8689 8c8f 9295 989b 9ea1 a4a7 aaad
b0b3 b6b9 bbbe c1c3 c6c9 cbce d0d2 d5d7
d9db dee0 e2e4 e6e7 e9eb ecee f0f1 f2f4
f5f6 f7f8 f9fa fbfb fcfd fdfe fefe fefe
fffe fefe fefe fdfd fcfb fbfa f9f8 f7f6
f5f4 f2f1 f0ee eceb e9e7 e6e4 e2e0 dedb
d9d7 d5d2 d0ce cbc9 c6c3 c1be bbb9 b6b3
b0ad aaa7 a4a1 9e9b 9895 928f 8c89 8683
807d 7a77 7471 6e6b 6865 625f 5c59 5653
504d 4a47 4542 3f3d 3a37 3532 302e 2b29
2725 2220 1e1c 1a19 1715 1412 100f 0e0c
0b0a 0908 0706 0505 0403 0302 0202 0202
0102 0202 0202 0303 0405 0506 0708 090a
0b0c 0e0f 1012 1415 1719 1a1c 1e20 2225
2729 2b2e 3032 3537 3a3d 3f42 4547 4a4d
5053 5659 5c5f 6265 686b 6e71 7477 7a7d
@tri-pcm
8082 8486 888a 8c8e 9092 9496 989a 9c9e
a0a2 a4a6 a8aa acae b0b2 b4b6 b8ba bcbe
c0c2 c4c6 c8ca ccce d0d2 d4d6 d8da dcde
e0e2 e4e6 e8ea ecee f0f2 f4f6 f8fa fcfe
fffd fbf9 f7f5 f3f1 efed ebe9 e7e5 e3e1
dfdd dbd9 d7d5 d3d1 cfcd cbc9 c7c5 c3c1
bfbd bbb9 b7b5 b3b1 afad aba9 a7a5 a3a1
9f9d 9b99 9795 9391 8f8d 8b89 8785 8381
7f7d 7b79 7775 7371 6f6d 6b69 6765 6361
5f5d 5b59 5755 5351 4f4d 4b49 4745 4341
3f3d 3b39 3735 3331 2f2d 2b29 2725 2321
1f1d 1b19 1715 1311 0f0d 0b09 0705 0301
0103 0507 090b 0d0f 1113 1517 191b 1d1f
2123 2527 292b 2d2f 3133 3537 393b 3d3f
4143 4547 494b 4d4f 5153 5557 595b 5d5f
6163 6567 696b 6d6f 7173 7577 797b 7d7f
@sierra-pcm
8282 8183 8384 8685 8888 8889 8a8b 8c8c
8e8e 8f90 9092 9193 9494 9596 9699 9899
9b9a 9c9c 9c9d 9ea0 a1a0 a2a2 a3a5 a4a6
a7a7 a9a8 a9aa aaac adad aeae b0b0 b1b3
b2b4 b5b5 b6b7 b9b8 b9bb babc bdbc bdbe
bfc1 bfc1 c3c1 c4c5 c5c6 c6c7 c9c7 cbca
cbcc cdcd cfcf d2d0 d2d2 d2d5 d4d5 d6d7
d8d8 d9dc d9df dadf dce1 dde5 dce6 dceb
cb1f 1b1e 1c21 1c21 1f23 2025 2127 2329
2529 2829 2a2b 2b2e 2d2f 302f 3231 3234
3334 3536 3836 3939 3a3b 3b3d 3e3d 3f40
4042 4242 4444 4646 4748 474a 4a4b 4d4c
4e4e 4f50 5052 5252 5554 5557 5759 5959
5b5b 5c5d 5d5f 5e60 6160 6264 6365 6566
6867 6969 6a6c 6c6d 6d6e 706f 7071 7174
7475 7576 7777 797a 7a7c 7b7c 7e7d 7f7f
```
=> https://git.sr.ht/~rabbits/uxn/tree/main/item/projects/examples/demos/piano.tal piano.tal código fuente
¿y qué significan estos números?
en el contexto de varvara, podemos entenderlos como múltiples bytes sin signo (u8) que corresponden a las amplitudes de la onda sonora que componen la muestra.
un "cabezal de reproducción" visita cada uno de estos números durante un tiempo determinado, y los utiliza para establecer la amplitud de la onda sonora.
las siguientes imágenes muestran la forma de la onda (o "waveform") de cada una de estas muestras.
cuando hacemos un bucle con estas formas de onda, ¡obtenemos un tono basado en su forma!
piano-pcm:
=> ./img/screenshot_uxn-waveform_piano.png forma de onda de la muestra de piano
violín-pcm:
=> ./img/screenshot_uxn-waveform_violin.png forma de onda de la muestra de violín
sin-pcm:
=> ./img/screenshot_uxn-waveform_sin.png forma de onda de la muestra de sin
tri-pcm:
=> ./img/screenshot_uxn-waveform_tri.png forma de onda de la muestra de tri
sierra-pcm:
=> ./img/screenshot_uxn-waveform_saw.png forma de onda de la muestra de sierra
de forma similar a como hemos tratado los sprites, y de forma parecida al dispositivo de archivo comentado anteriormente, para fijar una muestra en el dispositivo de audio sólo tenemos que escribir su dirección y su longitud:
```
;sierra-pcm .Audio0/direc DEO2 ( establecer la dirección de la muestra )
#0100 .Audio0/largo DEO2 ( establecer la longitud de la muestra )
```
la frecuencia a la que se reproduce esta muestra (es decir, a la que la amplitud de la onda toma el valor del siguiente byte) viene determinada por el byte tono.
## tono
el byte tono hace que la muestra comience a reproducirse cada vez que le escribimos, de forma similar a como el byte de sprite realiza el dibujo del sprite cuando le escribimos.
los primeros 7 bits (de derecha a izquierda) del byte corresponden a una nota midi, y por tanto a la frecuencia a la que se reproducirá la muestra.
el octavo bit es una bandera: cuando es 0 la muestra se reproducirá en bucle, y cuando es 1 la muestra se reproducirá sólo una vez.
normalmente querremos hacer un bucle de la muestra para generar un tono basado en ella. sólo cuando la muestra sea lo suficientemente larga tendrá sentido no hacer un bucle y reproducirla una vez.
con respecto a los bits para la nota midi, es una buena idea tener una tabla midi alrededor para ver los valores hexadecimales correspondientes a las diferentes notas.
=> https://wiki.xxiivv.com/site/midi.html tabla midi
el Do medio (C4, o 3c en midi) se asume como el tono por defecto de las muestras.
### un programa de "muestra"
en teoría, parecería que el siguiente programa debería reproducir nuestra muestra a esa frecuencia, ¿o no?
```
( hola-sonido.tal )
( dispositivos )
|30 @Audio0 [ &vector $2 &posición $2 &salida $1 &pad $3 &adsr $2 &largo $2 &direc $2 &volumen $1 &tono $1 ]
( programa principal )
|0100
;sierra-pcm .Audio0/direc DEO2 ( establecer la dirección de la muestra )
#0100 .Audio0/largo DEO2 ( establecer la longitud de la muestra )
#3c .Audio0/tono DEO ( establecer el tono como Do medio )
BRK
```
¡no realmente!
¡pero ya casi! para poder escuchar el sonido, necesitamos dos cosas más: ajustar el volumen del dispositivo y ajustar la envolvente ADSL.
## volumen
el byte de volumen se divide en dos nibbles: el nibble alto corresponde al volumen del canal izquierdo, y el nibble bajo corresponde al volumen del canal derecho.
por lo tanto, cada canal tiene 16 niveles posibles: 0 es el mínimo, y f el máximo.
lo siguiente establecería el volumen máximo en el dispositivo:
```
#ff .Audio0/volumen DEO ( establecer el volumen máximo en izquierda y derecha )
```
¡aunque las muestras son mono, podemos panoramizarlas con el byte de volumen para obtener un sonido estéreo!
## envolvente ADSR
el último componente que necesitamos para reproducir audio es la envolvente ADSR.
ADSR son las siglas de ataque, decaimiento, sostenimiento y relajación. es el nombre de una "envolvente" común que modula la amplitud de un sonido de principio a fin.
en el ordenador varvara, los componentes ADSR funcionan de la siguiente manera:
* la sección de ataque es el tiempo que tarda en pasar la amplitud del sonido que se está reproduciendo de 0 a 100%.
* luego, la sección de decaimiento es el tiempo que se necesita para llevar la amplitud del 100% al 50%.
* luego, durante la sección de sostenimiento la amplitud se mantiene al 50%
* y finalmente, en la sección de relajación la amplitud pasa del 50% al 0%.
cada una de estas transiciones se realiza de forma lineal.
en el corto ADSR del dispositivo de audio, hay un nibble para cada uno de los componentes: por lo tanto, cada uno puede tener una duración de 0 a f.
las unidades para estas duraciones son 15vos de segundo.
como ejemplo, si la duración del componente de ataque es 'f', entonces durará un segundo (15/15 de segundo, en decimal).
lo siguiente establecerá la duración máxima de cada uno de los componentes, haciendo que el sonido dure 4 segundos en total:
```
#ffff .Audio0/adsr
```
ok, ¡ahora estamos listos para reproducir el sonido!
## reproduciendo la muestra
¡el siguiente programa tiene ahora los cinco componentes que necesitamos para reproducir un sonido: una dirección de muestra, su longitud, las duraciones de adsr, el volumen, y su tono!
```
( hola-sonido.tal )
( dispositivos )
|30 @Audio0 [ &vector $2 &posición $2 &salida $1 &pad $3 &adsr $2 &largo $2 &direc $2 &volumen $1 &tono $1 ]
( programa principal )
|0100
;sierra-pcm .Audio0/direc DEO2 ( establecer la dirección de la muestra )
#0100 .Audio0/largo DEO2 ( establecer la longitud de la muestra )
#ffff .Audio0/adsr DEO2 ( establecer la envolvente )
#ff .Audio0/volumen DEO ( establecer el volumen máximo )
#3c .Audio0/tono DEO ( establecer el tono como Do central )
BRK
```
nota (!) que sólo se reproducirá el sonido una vez, y lo hace cuando se inicia el programa.
### algunos experimentos sugeridos
te invito a que experimentes modificando los valores del ADSR: ¿cómo cambia el sonido cuando sólo hay uno de ellos? ¿o cuando todos son números pequeños o con diferentes combinaciones de duraciones?
también, prueba a cambiar el byte tono: ¿corresponden con tus oídos los valores midi que esperas?
¿y cómo cambia el sonido cuando usas una muestra diferente? ¿puedes encontrar o crear otras diferentes?
## tocar más de una vez
una vez que hemos configurado nuestro dispositivo de audio con una muestra, longitud, envolvente ADSR y volumen, podríamos reproducirlo una y otra vez (re)escribiendo un tono en un momento diferente; los demás parámetros pueden dejarse intactos.
por ejemplo, una macro como la siguiente podría permitirnos reproducir una nota de nuevo según el tono dado en la parte superior de la pila:
```
%REPR-NOTA { .Audio0/tono DEO } ( tono -- )
```
cuando ocurriera un evento específico, podrías llamarlo:
```
#3c REPR-NOTA ( reproducir Do central )
```
ten en cuenta que cada vez que escribes un tono, la reproducción de la muestra y la forma de la envolvente vuelve a empezar, independientemente de dónde se encuentre.
### algunas ideas
¿qué tal si implementas la reproducción de diferentes tonos presionando diferentes teclas en el teclado? podrías usar nuestros ejemplos anteriores, pero escribiendo un tono en el dispositivo en lugar de, por ejemplo, incrementar una coordenada :)
¿o qué tal si complementas nuestro programa pong del {tutorial de uxn día 6} con efectos de sonido, haciendo que el dispositivo toque una nota cada vez que la pelota rebote?
¿o qué tal si utilizas el vector de la pantalla para cronometrar la reproducción repetitiva de una nota? ¿o qué tal si haces que toque una melodía siguiendo una secuencia de notas? ¿podría venir esta secuencia de un archivo de texto? :)
## información de la reproducción
el dispositivo de audio nos proporciona dos formas de comprobar el estado de la reproducción durante el tiempo de ejecución:
* el corto de posición
* el byte de salida
cuando leemos el corto de posición, obtenemos la posición actual de la "cabeza lectora" en la muestra, empezando por 0 (es decir, la cabeza lectora está al principio de la muestra) y terminando en la longitud de la muestra menos uno.
el byte de salida nos permite leer la amplitud de la envolvente. devuelve 0 cuando la muestra no se está reproduciendo, por lo que se puede utilizar como una forma de saber que la reproducción ha terminado.
## polifonía
la idea de tener cuatro dispositivos de audio es que podemos tenerlos todos sonando a la vez, y cada uno puede tener una muestra, envolvente ADSR, volumen y tono diferentes.
esto nos da muchas más posibilidades:
¿quizás en un juego podría haber una melodía sonando de fondo junto con sonidos incidentales relacionados con la jugabilidad?
¿tal vez se pueda construir un secuenciador en el que se puedan controlar los cuatro dispositivos como pistas diferentes?
¿o tal vez crear una plataforma de livecoding para tener un diálogo con cada uno de los cuatro instrumentos?
en cualquier caso, ¡no dudes en compartir lo que crees! :)
# el final
aunque no lo creas, ¡este es el final!
¡has llegado al final de este tutorial! ¡felicidades!
¡espero que lo hayas disfrutado y que lo veas como el comienzo de tu viaje uxn!
¡nos encantaría ver lo que creas! no dudes en compartirlo en mastodon, en el foro o en el canal irc (¡o incluso por correo electrónico!)
=> https://llllllll.co/t/uxn-virtual-computer/ uxn en el foro lines (inglés)
=> ./contacto.gmi {contacto}
canal irc: #uxn en irc.esper.net
pero antes de hacer todo esto, ¡no te olvides de tomar un descanso! :)
¡nos vemos pronto!
# apoyo
si te ha gustado este tutorial y te ha resultado útil, considera compartirlo y darle tu {apoyo} :)

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

@ -1,4 +1,4 @@
# uxn tutorial appendix b: repeating a tile inside a rectangle
# uxn tutorial appendix a: repeating a tile inside a rectangle
in the first part of {uxn tutorial day 6} we discuss how to cover the background of the screen with a given tile.
@ -20,7 +20,7 @@ let's start with the following program as a template. it includes the data for a
( hello-background.tal )
( devices )
|00 @System [ &vector $2 &pad $6 &r $2 &g $2 &b $2 ]
|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 ]
( macros )
@ -34,7 +34,6 @@ let's start with the following program as a template. it includes the data for a
#01c0 .System/g DEO2
#2ce5 .System/b DEO2
BRK
@tile-background 1122 4488 1122 4488
@ -167,8 +166,8 @@ the following shows our program in context, completely filling the first row of
( hello-background.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 ]
|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 ]
( macros )
%RTN { JMP2r }
@ -209,7 +208,7 @@ RTN
similar to what we just did: what's a procedure we could follow to repeat vertically a row starting from y, and ending at a limit corresponding to y+height?
following the same strategy, we could do
following the same strategy, we could do:
* draw row in y
* add 8 (the size of the tile) to y

View File

@ -8,14 +8,12 @@ we also jump right in into our first simple programs to demonstrate fundamental
or first of all... what is uxn?
> Uxn is a portable 8-bit virtual computer capable of running simple tools and games programmable in its own little assembly language. It is also playground to learn basic computation skills.
=> https://wiki.xxiivv.com/site/uxn.html XXIIVV - uxn
i invite you to read "why create a smol virtual computer" from the 100R site, as well:
> The Uxn ecosystem is a personal computing playground, created to host small tools and games, programmable in its own unique assembly language.
=> https://100r.co/site/uxn.html 100R - uxn
i invite you to read "why create a smol virtual computer" from that 100R site, as well.
uxn is the core of the varvara virtual computer. it is simple enough to be emulated by many old and new computing platforms, and to be followed by hand.
personally, i see in it the following features:
@ -160,11 +158,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

@ -45,6 +45,7 @@ let's see some examples!
### LIT2
first of all, let's recap. the following code will push number 02 down onto the stack, then it will push number 30 (hexadecimal) down onto the stack, and finally add them together, leaving number 32 in the stack:
```
#02 #30 ADD
```
@ -98,6 +99,7 @@ now, let's compare with what happens with ADD2:
```
in this case we are pushing the same 4 bytes down onto the stack, but ADD2 is doing the following actions:
* take the top element of the stack (08), and store it as the low byte of the first short
* take the new top element of the stack (00), and store it as the high byte of the first short, that is now 0008
* take the new top element of the stack (04), and store it as the low byte of the second short
@ -178,7 +180,7 @@ we could write that as follows:
( hello-screen.tal )
( devices )
|00 @System [ &vector $2 &pad $6 &r $2 &g $2 &b $2 ]
|00 @System [ &vector $2 &pad $6 &r $2 &g $2 &b $2 ]
( main program )
|0100
@ -308,7 +310,7 @@ let's try it all together! the following code will draw a pixel with color 1 in
#41 .Screen/pixel DEO
```
the complete program would look as follows:
the complete program would look as follows:
```
( hello-pixel.tal )
@ -575,7 +577,6 @@ the eight possible values of this high nibble, used for drawing a 1bpp sprite, a
+ <tr><td>6</td><td>foreground</td><td>yes</td><td>no</td></tr>
+ <tr><td>7</td><td>foreground</td><td>yes</td><td>yes</td></tr>
+ </table>
& * 0: draw a 1bpp sprite in the background, original orientation
& * 1: draw a 1bpp sprite in the background, flipped horizontally
& * 2: draw a 1bpp sprite in the background, flipped vertically
@ -647,7 +648,6 @@ note that 0 in the low nibble will clear the tile.
additionally, 5, 'a' and 'f' in the low nibble will draw the pixels that are "on" but will leave the ones that are "off" as is: this will allow you to draw over something that has been drawn before, without erasing it completely.
## hello sprite
let's do this! the following program will draw our sprite once:
@ -862,7 +862,6 @@ the eight possible values for this nibble are:
+ <tr><td>e</td><td>foreground</td><td>yes</td><td>no</td></tr>
+ <tr><td>f</td><td>foreground</td><td>yes</td><td>yes</td></tr>
+ </table>
& * 8: draw a 2bpp sprite in the background, original orientation
& * 9: draw a 2bpp sprite in the background, flipped horizontally
& * a: draw a 2bpp sprite in the background, flipped vertically
@ -897,7 +896,6 @@ the low nibble will allow us to choose between many combinations of colors assig
+ <tr><td>e</td><td>3</td><td>2</td><td>3</td><td>1</td></tr>
+ <tr><td>f</td><td>none</td><td>3</td><td>1</td><td>2</td></tr>
+ </table>
& * 0: colors 0, 0, 1, 2
& * 1: colors 0, 1, 2, 3
& * 2: colors 0, 2, 3, 1
@ -1157,7 +1155,6 @@ in {uxn tutorial day 3} we start working with interactivity using the keyboard,
however, i invite you to take a break, and maybe keep exploring drawing in the uxn screen via code, before continuing!
# support
if you enjoyed this tutorial and found it helpful, consider sharing it and giving it your {support} :)

View File

@ -24,6 +24,10 @@ the button byte encodes in each of its eight bits the state of eight different "
numbering the bits from right to left, and from 0 to 7, the corresponding keys (and NES buttons) are:
+ <table>
+ <tr><th>bit 7</th><th>bit 6</th><th>bit 5</th><th>bit 4</th><th>bit 3</th><th>bit 2</th><th>bit 1</th><th>bit 0</th></tr>
+ <tr><td>Right</td><td>Left</td><td>Down</td><td>Up</td><td>Esc (Start)</td><td>Shift (Select)</td><td>Alt (B)</td><td>Ctrl (A)</td></tr>
+ </table>
& * 0: Ctrl (button A)
& * 1: Alt (button B)
& * 2: Shift (Select button)
@ -33,11 +37,6 @@ numbering the bits from right to left, and from 0 to 7, the corresponding keys (
& * 6: Left
& * 7: Right
+ <table>
+ <tr><th>bit 7</th><th>bit 6</th><th>bit 5</th><th>bit 4</th><th>bit 3</th><th>bit 2</th><th>bit 1</th><th>bit 0</th></tr>
+ <tr><td>Right</td><td>Left</td><td>Down</td><td>Up</td><td>Esc (Start)</td><td>Shift (Select)</td><td>Alt (B)</td><td>Ctrl (A)</td></tr>
+ </table>
enconding the states of the buttons in this way allows us to press and read many of these keys at the same time.
## the key byte
@ -164,7 +163,7 @@ AND2, ORA2, EOR2 will work in the same way, but with shorts instead of bytes.
### AND
the following will push down into the stack a flag that indicates if the key byte is between 30 and 39 inclusive, using 01 to represent 'true', and 00 to represent 'false0:
the following will push down into the stack a flag that indicates if the key byte is between 30 and 39 inclusive, using 01 to represent 'true', and 00 to represent 'false':
```
.Controller/key DEI ( read key and push into the stack )
@ -393,12 +392,12 @@ so far we have been using the stack as a place to store operands of instructions
uxntal has six instructions that act upon elements in the stack closer to the top:
* POP: Remove top element from the stack ( a -- )
* DUP: Duplicate; push a copy of the top element ( a -- a a )
* SWP: Swap; change the order of the top two elements of the stack ( a b -- b a )
* NIP: Remove the top second element of the stack ( a b -- b )
* OVR: Over; push a copy of the second top element ( a b -- a b a )
* ROT: Rotate; reorder the top three elements of the stack so that the third one is now at the top ( a b c -- b c a )
* POP: remove top element from the stack ( a -- )
* DUP: duplicate; push a copy of the top element ( a -- a a )
* SWP: swap; change the order of the top two elements of the stack ( a b -- b a )
* NIP: remove the top second element of the stack ( a b -- b )
* OVR: over; push a copy of the second top element ( a b -- a b a )
* ROT: rotate; reorder the top three elements of the stack so that the third one is now at the top ( a b c -- b c a )
in short mode, POP2, DUP2, SWP2, NIP2, OVR2 and ROT2 perform the same actions but using shorts instead of bytes.
@ -460,7 +459,7 @@ now the stack looks like this:
flag1 key <- top
```
finally we can proceed with the comparison and the AND
finally we can proceed with the comparison and the AND:
```
#3a LTH ( is it less than 3a? push flag into the stack )
@ -754,6 +753,7 @@ these are all the uxntal instructions that we discussed today!
* POP: Remove top element from the stack ( a -- )
* DUP: Duplicate; push a copy of the top element ( a -- a a )
* SWP: Swap; change the order of the top two elements of the stack ( a b -- b a )
* NIP: Remove the top second element of the stack ( a b -- b )
* OVR: Over; push a copy of the second top element ( a b -- a b a )
* ROT: Rotate; reorder the top three elements of the stack so that the third one is now at the top ( a b c -- b c a )

View File

@ -192,7 +192,7 @@ as you may recall, the zero page consists of the first 256 addresses of program
we can refer to any of the 256 addresses of the zero page using one byte only, instead of the two bytes that are needed for absolute addresses.
something importat to keep in mind is that the contents of the zero page are not present in uxn roms.
something important to keep in mind is that the contents of the zero page are not present in uxn roms.
this means that a caveat of using variables there, is that in order to initialize them we need to do it during runtime, by storing values from the stack into them.
@ -207,7 +207,6 @@ labels for the zero page would work the same as before; we only need to specify
in order to refer to them, we would use the dot (.) rune for literal zero page addresses, instead of the colon (;) rune for literal absolute addresses.
### instructions: LDZ, STZ
the instructions for loading (reading) and storing (writing) from and to the zero page are:
@ -266,7 +265,7 @@ BRK
note the use of the literal zero page address rune (.) to refer to the .pixel label.
also, note that in the case of .pixel the address is referring to the zero page, accessed with LDZ/STZ, and in the case of .Screen the address is referring to the i/o address space, accessed with DEO/DEI.
also, note that in the case of .pixel the address is referring to the zero page, accessed with LDZ/STZ, and in the case of .Screen the address is referring to the i/o address space, accessed with DEI/DEO.
### a little stack wrangling practice
@ -358,7 +357,7 @@ the use of "variables" will help us now in discussing three different ways of an
* autonomous change of position
* interactive change of position (with keyboard)
* autonomous change of drawn tile
* autonomous change of drawn tile
we will review them separately in order to keep the examples relatively simple and readable.
@ -920,12 +919,10 @@ we can use these macros to divide the frequency in our code:
.Screen/addr DEO2 ( set computed address )
```
=> ./img/screencap_uxn-animation-quarterspeed.gif animation of a diagonal stripe inside a pixelated square. the diagonal moves from bottom right to top left. it moves slower than the previous one.
ah, way better!
## not powers of two
note that if you want to divide the frequency to numbers that are not powers of 2, you might start to see some glitches approximately every 4 seconds: this is due to framecount overflowing and not giving a nice sequence of results for those divisors.
@ -936,7 +933,6 @@ the easiest workaround for these issues would be to use a short-sized framecount
you'd have to adapt the program to work with that size of framecount - nice exercise, i feel and think!
# instructions of day 4
these are all the uxntal instructions that we discussed today!

View File

@ -6,32 +6,57 @@ 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.
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:
* 01 when the left button is pressed
* 10 when the right button is pressed
* 11 when both buttons are pressed
* 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 three 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 +71,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 +89,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 +110,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 +141,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 +157,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 +207,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 +217,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!
@ -234,6 +259,14 @@ first of all, let's move our pointer drawing subroutine to another label in our
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,14 +275,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
with what we know already, and depending on the position of draw-pointer with respect to on-mouse, we could do a relative jump:
@ -282,14 +307,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 +363,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 +397,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 +415,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 +470,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 +488,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 +517,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 +578,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,9 +625,9 @@ 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
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:
```
a - ( a/b )*b
@ -586,9 +635,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 +651,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 +666,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 +698,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 +726,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 +744,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 +756,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 }
@ -46,7 +45,6 @@ let's start with the following program as a template. it includes the data for a
#01c0 .System/g DEO2
#2ce5 .System/b DEO2
BRK
@tile-background 1122 4488 1122 4488
@ -62,7 +60,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 +75,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 +85,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 +98,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 +121,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 +174,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 +235,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 +246,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 +322,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 +345,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 +372,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 +503,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 +517,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 +525,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 +562,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 +573,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 }
@ -673,7 +675,7 @@ all of this can go inside its own subroutine for readability purposes:
```
@update-paddles ( -- )
&left
( left paddle: up and down buttons )
( left paddle: up and down buttons )
.Controller/button DEI
DUP #10 AND ( check bit for up )
,&left-up JCN
@ -770,15 +772,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 +794,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 +849,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 +869,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 +903,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 +922,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 +937,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 +956,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 +981,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 +999,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 +1014,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 +1077,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 +1090,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 +1120,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
@ -1178,8 +1189,7 @@ where bounce-left would be:
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.
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 +1207,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
@ -1208,8 +1219,7 @@ the whole x-in-left code would end up looking like:
&check-right-paddle
```
finish would be a label at the end of the subroutine, and reset is a subroutine that we will discuss later.
"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.
@ -1244,7 +1254,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
@ -1291,8 +1302,7 @@ here's all of the code we wrote today!
|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 ]
|80 @Controller [ &vector $2 &button $1 &key $1 ]
( macros )
%RTN { JMP2r }
@ -1671,7 +1681,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 stat short is similar to load, but reads the directory entry for the filename
* 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 read, 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! :)

View File

@ -11,8 +11,7 @@ the site is available in both the gemini protocol and the web
it is hosted in el caracolito:
=> gemini://caracolito.mooo.com caracolito (gemini)
=> https://caracolito.mooo.com caracolito (web)
=> //caracolito.mooo.com caracolito
like compudanzas, this wiki is always in construction, see the {roadmap} for what wants to happen.