💾 Archived View for compudanzas.net › uxn_tutorial_day_5.gmi captured on 2023-06-14 at 14:00:42. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2023-03-20)
-=-=-=-=-=-=-
en español:
this is the fifth section of the uxn tutorial! here we introduce the varvara mouse device to explore more possible interactions, and we cover the remaining elements of uxntal and uxn: the return stack, the return mode and the keep mode.
we also discuss possible structures to create loops and more complex programs using these resources!
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 &pad $3 &scrollx $2 &scrolly $2 ]
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:
note that similarly to the controller device, this system allows us to check for several buttons pressed at once:
remember that we can use AND masks, as introduced on uxn tutorial day 3, to isolate and evaluate separately any of these bits.
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")
similarly, scrollx will indicate an horizontal scroll
depending on the device, the values can be greater than 0001 or less than ffff depending on the speed of the scroll.
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 elements:
combined with a conditional and sprite drawing.
it draws our square in the position of the mouse, changing its color when any mouse button is pressed.
( hello-mouse.tal ) ( 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 &pad $3 &scrollx $2 &scrolly $2 ] ( init ) |0100 ( set system colors ) #2ce9 .System/r DEO2 #01c0 .System/g DEO2 #2ce5 .System/b DEO2 ( set mouse vector ) ;on-mouse .Mouse/vector DEO2 ( set sprite address ) ;square .Screen/addr DEO2 BRK @on-mouse ( -> ) .Mouse/x DEI2 .Screen/x DEO2 .Mouse/y DEI2 .Screen/y DEO2 ( jump if any button is pressed ) .Mouse/state DEI ,&pressed JCN ( draw sprite using color 2 and 0 in background ) #02 .Screen/sprite DEO BRK &pressed ( draw sprite using color 1 and 0 in background ) #01 .Screen/sprite DEO BRK @square [ ff81 8181 8181 81ff ]
maybe you have noticed that, before today, the mouse pointer has disappeared when entering the uxnemu window.
how could we program and replicate its behavior in uxntal?
we could use a similar strategy to what we did for animating a sprite:
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 the data of a 1bpp sprite of a mouse pointer, taken from the uxn examples:
@pointer_icn [ 80c0 e0f0 f8e0 1000 ]
this is a program that accomplishes drawing the pointer on the screen!
( hello-pointer.tal ) ( 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 &pad $3 &scrollx $2 &scrolly $2 ] ( zero page ) |0000 @pointer [ &x $2 &y $2 ] ( init ) |0100 ( set system colors ) #2ce9 .System/r DEO2 #01c0 .System/g DEO2 #2ce5 .System/b DEO2 ( set mouse vector ) ;on-mouse .Mouse/vector DEO2 ( set sprite address ) ;pointer_icn .Screen/addr DEO2 BRK @on-mouse ( -> ) ( send pointer position to screen ) .pointer/x LDZ2 .Screen/x DEO2 .pointer/y LDZ2 .Screen/y DEO2 ( clear sprite from foreground ) #40 .Screen/sprite DEO ( update pointer position ) .Mouse/x DEI2 .pointer/x STZ2 .Mouse/y DEI2 .pointer/y STZ2 ( send pointer position to screen ) .pointer/x LDZ2 .Screen/x DEO2 .pointer/y LDZ2 .Screen/y DEO2 ( draw sprite with color 2 in foreground ) #4a .Screen/sprite DEO BRK @pointer_icn [ 80c0 e0f0 f8e0 1000 ]
note that it draws the pointer in the foreground, and that it uses 'a' in the low nibble of the sprite byte: this implies that it will use color 2 to draw the pointer shape, and will draw with transparency the rest of the tile. (see drawing 1bpp sprites in uxn tutorial day 2 )
this blending mode would allow you to draw things in the background and have the pointer cover them with its shape only, and not with the whole square tile.
i invite you to try this!
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!
now, we can see that the program does work, but it is kind of flooding the on-mouse subroutine with a lot of code.
that's even more true if we consider that we are only drawing the pointer, and not taking any other action like responding to the buttons.
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 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!
so far, "the stack" that we have been using is what is usually called "the working stack".
uxn, like other forth-like systems, has another stack of the same size, "the return stack".
why is it called like that?
the idea is that it would be normally used to push down into it addresses of program memory.
these addresses would correspond to locations to which we'd like to eventually "return".
first of all, let's move our pointer drawing subroutine to another label in our program:
@draw-pointer ( -- ) ( send pointer position to screen ) .pointer/x LDZ2 .Screen/x DEO2 .pointer/y LDZ2 .Screen/y DEO2 ( clear sprite from foreground ) #40 .Screen/sprite DEO ( update pointer position ) .Mouse/x DEI2 .pointer/x STZ2 .Mouse/y DEI2 .pointer/y STZ2 ( send pointer position to screen ) .pointer/x LDZ2 .Screen/x DEO2 .pointer/y LDZ2 .Screen/y DEO2 ( draw sprite with color 2 in foreground ) #4a .Screen/sprite DEO BRK
note that we could join the actions of updating the pointer position and sending it to the screen, using a pair of DUP2:
( update pointer position and send to screen ) .Mouse/x DEI2 DUP2 .pointer/x STZ2 .Screen/x DEO2 .Mouse/y DEI2 DUP2 .pointer/y STZ2 .Screen/y DEO2
this would leave our on-mouse subroutine empty:
@on-mouse ( -> ) BRK
with what we know already, and depending on the position of draw-pointer with respect to on-mouse, we could do a relative jump:
@on-mouse ( -> ) ,draw-pointer JMP &return ( something else here ) BRK
or an absolute jump:
@on-mouse ( -> ) ;draw-pointer JMP2 &return ( something else here ) BRK
the thing is, if we want something else to happen after drawing the pointer inside on-mouse, we can't go back easily.
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!
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.
for example, our jumps could be re-written as follows. in the case of a relative jump:
@on-mouse ( -> ) ,draw-pointer JSR ( something else here ) BRK
and in the case of an absolute jump:
@on-mouse ( -> ) ;draw-pointer JSR2 ( something else here ) BRK
JSR is pushing the "return address" down into the return stack.
now the question is: how do we take that address from that stack, in order to jump there?
we'd need to use the "return mode"!
similar to the short mode, the return mode in uxn consists in activating a binary flag in the byte that encodes an instruction.
the return mode is encoded in the 7th bit of an instruction byte, counting from right to left.
whenever this flag is set, uxn will perform the given instruction but using as sources the contents of the return stack instead of the contents of the working stack.
in uxntal, we indicate that we want to set this flag adding the letter 'r' to the end of an instruction mnemonic.
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'.
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 :)
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 that is present in the return stack?
exactly!
activating the return mode in the JMP instruction!
additionally, as the addresses pushed by JSR are shorts, we need to activate the short mode as well:
JMP2r ( jump to the absolute address at the top of the return stack )
in many uxntal programs you will see this instruction written as a macro, RTN (return):
%RTN { JMP2r }
we can finish a subroutine using this macro in order to "return" to the position in the program after the corresponding JSR.
this is the hello-pointer.tal program, but using draw-pointer as a subroutine that is "called" with JSR2 and that ends with RTN:
( hello-pointer.tal ) ( 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 &pad $3 &scrollx $2 &scrolly $2 ] ( macros ) %RTN { JMP2r } ( zero page ) |0000 @pointer [ &x $2 &y $2 ] ( init ) |0100 ( set system colors ) #2ce9 .System/r DEO2 #01c0 .System/g DEO2 #2ce5 .System/b DEO2 ( set mouse vector ) ;on-mouse .Mouse/vector DEO2 ( set sprite address ) ;pointer_icn .Screen/addr DEO2 BRK @on-mouse ( -> ) ;draw-pointer JSR2 ( or ,draw-pointer JSR ) ( something else ) BRK @draw-pointer ( -- ) ( send pointer position to screen ) .pointer/x LDZ2 .Screen/x DEO2 .pointer/y LDZ2 .Screen/y DEO2 ( clear sprite from foreground ) #40 .Screen/sprite DEO ( update pointer position ) .Mouse/x DEI2 .pointer/x STZ2 .Mouse/y DEI2 .pointer/y STZ2 ( send pointer position to screen ) .pointer/x LDZ2 .Screen/x DEO2 .pointer/y LDZ2 .Screen/y DEO2 ( draw sprite with color 2 in foreground ) #4a .Screen/sprite DEO RTN @pointer_icn [ 80c0 e0f0 f8e0 1000 ]
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.
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 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.
having introduced the return stack and the return mode, another world of possibilities opens for us: we can also use the return stack as an additional, temporary stack, to store some values while we operate on others.
in order to achieve this, uxn has an instruction called STH, stash.
this is the last instruction of uxn that we had to cover in this tutorial series! :)
STH takes a value from the working stack and pushes it down into the return stack.
in return mode, STHr does the opposite: it takes a value from the return stack, and pushes it down into the working stack.
and, as you might have guessed, in short mode this instruction operates moving shorts instead of bytes.
the following is an example subroutine that shows some possibilities of the return stack and mode.
it is a subroutine that draws an horizontal line of a given length (from 1 to 255 pixels, i.e. using one byte), starting from a given x coordinate (short) and using a given y coordinate (short).
these parameters are given as arguments in the working stack.
the subroutine uses the return stack to "stash" one of these arguments while working with the others.
additionally, it has a working loop! written in one of several ways of implementing it :)
the state of both the working (ws) and the return (rs) stacks is shown in the comments after almost every step. the top of the stacks is located at their right.
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: ) ( store length in return stack ) STH ( ws: x^ y^ / rs: length ) ( set initial coordinates ) .Screen/y DEO2 ( ws: x^ / rs: length ) .Screen/x DEO2 ( ws: / rs: length ) ( retrieve length from return stack ) STHr ( ws: length / rs: ) ( initialize count ) #00 ( ws: length 00 / rs: ) &loop ( stash length and count ) STH2 ( ws: / rs: length count ) ( draw pixel with color 2 ) #02 .Screen/pixel DEO ( increment x ) .Screen/x DEI2 INC2 .Screen/x DEO2 ( retrieve length and count ) STH2r ( ws: length count / rs: ) ( increment count to get new count ) INC ( ws: length count / rs: ) ( duplicate length and count, compare, and jump ) DUP2 ( ws: length count length count / rs: ) NEQ ( ws: length count flag / rs: ) ,&loop JCN ( ws: length count / rs: ) POP2 ( ws: / rs: ) RTN
in order to call the subroutine, you could do something like the following:
#0008 ( push initial x ) .Screen/height DEI2 HALF2 ( push y ) #ff ( push length of line ) ;draw-horizontal-line JSR2 ( call subroutine )
note that in this specific subroutine, the use of STH2 and STH2r after the &loop sublabel is actually not needed: the operations in between these instructions do touch the working stack but afterwards leave it as it was.
however, it shows how we can use these instructions to have a clean working stack without other values interfering.
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.
in uxntal, we indicate that we want to set this flag adding the letter 'k' to the end of an instruction mnemonic.
whenever this flag is set, uxn will perform the given instruction but "keeping" the original values in the corresponding stack.
in other words, in keep mode items will not be consumed from the stack, but the corresponding results will be pushed down into the stack.
keep mode can be combined with the other modes, for a total of eight possible combinations of modes.
we know what the following uxntal code does; it pushes 01 and 02 down into the stack, adds both elements, and pushes the result (03) down into the stack:
#01 #02 ( ws: 01 02 ) ADD ( ws: 03 )
compare with what happens when using ADDk instead:
#01 #02 ( ws: 01 02 ) ADDk ( ws: 01 02 03 )
the addition is performed and the result is pushed, but the operands are left in the stack.
it might be hard to think in general of a use for this, but "keep" it in mind!
actually, if you remember, on uxn tutorial day 4 i shared with you a couple of macros to perform a modulo operation:
%MOD { DUP2 DIV MUL SUB } ( a b -- a%b ) %MOD2 { OVR2 OVR2 DIV2 MUL2 SUB2 } ( a b -- a%b )
i said then that there was a more optimized set, and that we'd discuss it later.
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:
a - ( a/b )*b
try for example replacing 'a' with 7 and 'b' with 3; the modulo or remainder should be 1
in our original macro, what happens goes as follows:
#07 #03 ( ws: 07 03 ) DUP2 ( ws: 07 03 07 03 ) DIV ( ws: 07 03 02 ) MUL ( ws: 07 06 ) 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 in the division, so that we can perform the multiplication and subtraction later.
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 ) DIVk ( ws: 07 03 02 ) MUL ( ws: 07 06 ) SUB ( ws: 01 )
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:
%MOD { DIVk MUL SUB } %MOD2 { DIV2k MUL2 SUB2 }
keep mode can be useful when we do comparisons and we don't want to lose the original values.
for example, in our draw-horizontal-line subroutine, we had the following set of lines of code:
( duplicate length and count, compare, and jump ) DUP2 ( ws: length count length count / rs: ) NEQ ( ws: length count flag / rs: ) ,&loop JCN ( ws: length count / rs: )
you'll see that here, like in the DIVk case above, the DUP2 is there only to make sure that the length and count are not lost when performing NEQ.
we could therefore replace DUP2 NEQ with NEQk:
( duplicate length and count, compare, and jump ) NEQk ( ws: length count flag / rs: ) ,&loop JCN ( ws: length count / rs: )
sometimes we'll want to stash a copy of a value that we'll use at the moment.
without keep mode, we'd write:
DUP STH ( duplicate and stash )
with keep mode, we can write:
STHk ( stash and keep )
similarly, there will be occasions where we want to retrieve a copy of a value from the return stack.
for that, we can write:
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!
with what we have covered already, and in case you want some ideas, here are some things that should be easier to try building now:
drawing a sprite consisting of several tiles is a process that can benefit from using subroutines: have a subroutine that receives a pair of x,y coordinates in the working stack when called, and use it to draw the tiles in the corresponding positions relative to those coordinates.
a lot of possibilities here!
maybe start with drawing only when a button is pressed. change color and/or "brush" depending on the button that is pressed.
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? 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 :)
how could you use the mouse wheel as an aid for drawing?
basically, the gates for interactive visual applications in the varvara computer are completely open to you now :)
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, 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 :)
we'd love to see what you create!
these are the uxntal instructions that we discussed today! with these, we have covered them all!
in uxn tutorial day 6 we talk about how we can integrate everything that we have covered in order to create even more complex subroutines and programs for the varvara computer.
we base our discussion in a recreation of the classic pong game!
besides using previous strategies and snippets of code, we cover strategies for drawing and controlling multi-tile sprites, and for checking collisions.
first, i invite you to take a break!
then, keep exploring, and share your findings!
if you enjoyed this tutorial and found it helpful, consider sharing it and giving it your support :)
uxn tutorial deprecated appendix a
text, images, and code are shared with the peer production license