working on day 5

This commit is contained in:
sejo 2021-08-24 19:52:52 -05:00
parent a3d6b4588c
commit b0f44f0ae7
4 changed files with 668 additions and 20 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@ -27,7 +27,7 @@ loop with a structure that allows you to easily use the iteration counter:
( to use the counter again, retrieve/keep it with STHkr )
STHr #01 ADD ( retrieve and increment counter )
STHr INC DUP ( retrieve and increment counter )
#07 LTH ( while condition )
,&loop JCN
POP

View File

@ -109,6 +109,11 @@ this is a summary of the uxn instructions covered in each day of the tutorial.
* LDR: load and push down into the stack the value at the given relative address ( address -- value )
* STR: store into the given relative address the value at the top of the stack ( value address -- )
## day 5
* JSR: unconditionally jump to the address in the working stack, pushing down into the return stack the address of the next instruction in memory
* STH: take a value from the working stack and push it down into the return stack. in return mode, do the opposite.
# draft outline

View File

@ -1,16 +1,14 @@
# uxn tutorial: day 5, the mouse and uxntal goodies
(work in progress)
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 using these resources!
we also discuss possible structures to create loops and more complex programs using these resources!
# the mouse device
the mouse device in the varvara computer has some similarities with the controller device: 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, wheel movement) and a couple of bytes to check its state.
it also has a couple of shorts corresponding to the x,y coordinates of the mouse pointer.
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:
@ -20,7 +18,7 @@ we see this device defined in uxntal in the following way:
## state byte
the state byte has the possible values:
the state byte has these possible values:
* 01 when the left button is pressed
* 10 when the right button is pressed
@ -29,7 +27,7 @@ the state byte has the possible values:
## wheel byte
the wheel byte has the possible values:
the wheel byte has these possible values:
* 01 when the mouse wheel is moving upwards
* ff when the mouse wheel is moving downwards
@ -44,32 +42,677 @@ the mouse vector will be fired in any of the following events:
* the mouse was moved
* the mouse wheel was moved
## hello mouse
here's a 1bpp sprite of a mouse pointer, taken from the uxn examples:
let's start drawing!
the following is a simple example that illustrates the use of
* mouse vector
* mouse x and y coordinates
* mouse state (pressed or not pressed)
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.
=> ./img/screenshot_uxn-draw-with-mouse.png screenshot showing a drawing made with the program: wiggly lines composed of overlapped squares of two different colors
```
( 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 &wheel $1 ]
( 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 a 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 ]
```
## hello pointer
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:
* clear sprite from previous position
* update position
* draw sprite in new position
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:
```
@pointer_icn [ 80c0 e0f0 f8e0 1000 ]
```
* mouse device and vector
* return stack and mode
* subroutines: parameters, calling, returning
* keep mode
* loops
* practice: sprite as pointer
* practice: simple drawing tool
### the program
new instructions: STH, JSR
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 &wheel $1 ]
( 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 in the background and see how the pointer looks, and then replace its sprite byte with e.g. 42 to see the difference!
### some issues
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 ends 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!
# the 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".
## preparing our program
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
```
this would leave our on-mouse subroutine empty:
```
@on-mouse ( -> )
BRK
```
### 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:
```
@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
```
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.
in the normal mode, JSR takes a relative address (one byte) from the working stack, and in short mode, JSR2 takes an absolute address.
## relative jump
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
```
## absolute jump
and in the case of an absolute jump:
```
@on-mouse ( -> )
;draw-pointer JSR2
( something else here )
BRK
```
## returning
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"!
# 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'.
## 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?
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 )
```
normally, in 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.
## complete example using subroutines
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 &wheel $1 ]
( 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.
## 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.
there might be other instances where using "variables" would make more logical and/or readable sense to pass arguments and results.
# stash, don't jump
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 we had to cover in this tutorial series! :)
## STH
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.
## example: horizontal line
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.
an asterisk (*) 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
```
### calling
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 )
```
### notes
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.
### possible exercises
* 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
# the keep mode
the last element of uxntal that we can 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.
## keep mode in arithmetic
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!
### modulo
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 later 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
* 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:
```
#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, 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!
```
#07 #03 ( ws: 07 03 )
DIVk ( ws: 07 03 02 )
MUL ( ws: 07 06 )
SUB ( ws: 01 )
```
so our macro can have one byte less!
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 and comparisons
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: )
```
## keep mode and the return stack
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 )
```
## more and more keep mode
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!
# more exercises
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:
## multi-tile sprites
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.
## drawing tool
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?
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?
## and more...
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, (i feel-think) 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!
# instructions of day 5
these are all the uxntal instructions that we discussed today!
these are the uxntal instructions that we discussed today! with these, we have covered them all!
* STH
* JSR
* JSR: unconditionally jump to the address in the working stack, pushing down into the return stack the address of the next instruction in memory
* STH: take a value from the working stack and push it down into the return stack. in return mode, do the opposite.
# coming soon: day 6
the following days of the <(uxn tutorial)> will consist in discussing all the remaining devices in the varvara computer.
meanwhile, i invite you to keep exploring, to share your findings, and to also take a break!
stay tuned!