tutorial day 4

This commit is contained in:
sejo 2021-08-11 20:00:55 -05:00
parent 6d77d1cccf
commit d6751f5bb7
3 changed files with 980 additions and 3 deletions

View File

@ -36,8 +36,65 @@ we also talk about logic and stack manipulation instructions in uxntal.
# day 4
here we discuss the animation loop of the varvara computer, via its screen device vector!
we also talk about using the program memory as an space for data via "variables", in order to have some persistency of data during the runtime of our programs, and/or in order to save us from complex stack wrangling :)
=> ./uxn_tutorial_day_4.gmi {uxn tutorial day 4}
# day 5
coming soon!
# instructions
this is a summary of the uxn instructions covered in each day of the tutorial.
## day 1
* ADD: take the top two elements from the stack, add them, and push down the result ( a b -- a+b )
* SUB: take the top two elements from the stack, subtract them, and push down the result ( a b -- a-b )
* LIT: push the next byte in memory down onto the stack
* DEO: output the given value into the given device address, both taken from the stack ( value address -- )
## day 2
* DEI: read a value into the stack, from the device address given in the stack ( address -- value )
* BRK: break the flow of the program, in order to close subroutines
* MUL: take the top two elements from the stack, multiply them, and push down the result ( a b -- a*b )
* DIV: take the top two elements from the stack, divide them, and push down the result ( a b -- a/b )
* SFT: take a shift value and a number to shift with that value, and shift it. the low nibble of the shift value indicates the shift to the right, and the high nibble the shift to the left ( number shift -- shiftednumber )
## day 3
* EQU: push 01 down into the stack if the top two elements of the stack are equal, 00 otherwise ( a b -- a==b )
* NEQ: push 01 down into the stack if the top two elements of the stack are not equal, 00 otherwise ( a b -- a!=b )
* GTH: push 01 down into the stack if the first element is greater than the second, 00 otherwise ( a b -- a>b )
* LTH: push 01 down into the stack if the first element is less than the second, 00 otherwise ( a b -- a>b )
* AND: perform a bitwise AND with the top two elements of the stack, and push down the result ( a b -- a&b )
* ORA: perform a bitwise OR with the top two elements of the stack, and push down the result ( a b -- a|b )
* EOR: perform a bitwise exclusive-OR with the top two elements of the stack, and push down the result ( a b -- a^b )
* JMP: unconditionally jump to the address in the stack ( addr -- )
* JCN: take an address and a value from the stack, and jump to the address if the value is not 00; otherwise continue with the next instruction ( value addr -- )
* 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 )
* 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 )
## day 4
* LDA: load and push down into the stack the value at the given absolute address ( address -- value )
* STA: store into the given absolute address the value at the top of the stack ( value address -- )
* LDZ: load and push down into the stack the value at the given zero page address ( address -- value )
* STZ: store into the given zero page address the value at the top of the stack ( value address -- )
* 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 -- )
# draft outline
this outline is here and now as a reference of the overall structure of the tutorial.

View File

@ -722,11 +722,13 @@ these are all the uxntal instructions that we discussed today!
* 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 )
# coming soon: day 4
# day 4
in the next section of the {uxn tutorial} we'll cover the use of the screen vector in order to create animation (interactive or not), and we'll explore possibilities for creating repetitive structures in uxntal.
in {uxn tutorial day 4} we cover the use of the screen vector in order to create animation, either interactive or not!
meanwhile, i invite you to keep exploring and to also take a break!
we also explore possibilities for using "variables" in uxntal than can help us creating more elaborate programs!
before jumping in, i invite you to keep exploring and to also take a break!
stay tuned!

918
src/uxn_tutorial_day_4.gmo Normal file
View File

@ -0,0 +1,918 @@
# uxn tutorial: day 4, variables and animation loop
this is the fourth section of the {uxn tutorial}!
here we discuss the animation loop of the varvara computer, via its screen device vector.
we also talk about using the program memory as an space for data via "variables", in order to have some persistency of data during the runtime of our programs, and/or in order to save us from complex stack wrangling :)
# the screen vector
we discussed the varvara screen device on day 2, but we skipped its vector port in order to focus in how to draw with it.
```
|20 @Screen [ &vector $2 &width $2 &height $2 &pad $2 &x $2 &y $2 &addr $2 &pixel $1 &sprite $1 ]
```
now that we have covered devices vectors on day 3, let's jump right in into how to use the one in the screen!
the following line of uxntal would assign the absolute address of the label on-frame to the screen vector:
```
;on-frame .Screen/vector DEO2
```
uxn will jump to that label at a rate of 60 times per second: we can use the subroutine under on-frame to change the contents of the screen, generating animation, and/or we can also use it for other timing-related purposes.
## a line that grows
the following program demonstrates a basic but powerful use of the screen vector: on each frame, it draws a pixel at the given screen x,y coordinates, and it adds 1 to the value of Screen/x:
```
( hello-line.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 ]
( init )
|0100
( set system colors )
#2ce9 .System/r DEO2
#01c0 .System/g DEO2
#2ce5 .System/b DEO2
( set initial x,y coordinates )
#0008 .Screen/x DEO2
#0008 .Screen/y DEO2
( set screen vector )
;on-frame .Screen/vector DEO2
BRK
@on-frame ( -> )
( draw a pixel in the bg with color 1 )
#01 .Screen/pixel DEO
( increment Screen/x )
.Screen/x DEI2 #0001 ADD2 .Screen/x DEO2
BRK
```
note that the code is very similar to the one we wrote on day 2 for drawing a line. in that one, we manually incremented the value of Screen/x in order to draw 6 pixels.
here, the same code for incrementing Screen/x is called instead inside the on-frame subroutine, having it occurring 60 times per second.
some change for you to try and practice:
* how would you make the line grow vertically? and diagonally?
* how would you make the line grow in the opposite direction?
* how would you make it a dotted line?
* how would you make the line stop growing at a certain position? (remember conditional jumps!)
* how would you draw using a sprite instead of a pixel?
# variables
the varvara screen vector opens up a whole world of possibilities! it's worth noting that many of these possibilities require ways of storing and retrieving data between frames.
in the previous example, we are using the screen ports for the x and y coordinates as a way of storing the coordinates for the pixel.
but what happens when we want to draw different objects, each with its own set of coordinates and other characteristics that can change over time?
we can use labels in program memory in order to achieve that!
## variables with absolute addresses
in a way, when storing our sprite data we have been doing something like that already.
we have labelled a section of memory with contents that are not instructions for uxn, but data; e.g.:
```
@square ff81 8181 8181 81ff
```
however, we have not used the data directly; we have sent its address to the corresponding screen device port.
### labels
we could use a similar system for storing e.g. x and y coordinates instead of sprite data:
```
@pixel-x 0008
@pixel-y 0008
```
or if we didn't want to initialize them here, we could define them as follows:
```
@pixel-x $2
@pixel-y $2
```
remember that $2 creates a relative pad of two byes, such that pixel-y is a label for an address in memory two bytes after pixel-x.
we could also use labels and sublabels, in a manner that would be very similar to how we define devices and their ports:
```
@pixel [ &x $2 &y $2 ]
```
### instructions: LDA and STA
how could we read (load) and write (store) the contents of the memory at those labels?
here are the two instructions that would help us with that:
* LDA: load and push down into the stack the value at the given absolute address ( address -- value )
* STA: store into the given absolute address the value at the top of the stack ( value address -- )
as we have discussed already, an absolute address will always be two bytes long.
in the short mode, LDA2 will load a short from the given address, and STA2 will store a short into the given address.
### examples
as an example, the following code would read the two bytes from pixel/x, increment them by one, and store them back into pixel/x:
```
;pixel/x LDA2 ( load pixel/x into the stack )
#0001 ADD2 ( increment )
;pixel/x STA2 ( store the result into pixel/x )
BRK
@pixel [ &x $2 &y $2 ]
```
note the use of BRK before the pixel label in order to make uxn stop before reading the data as instructions.
the following is a variation that also duplicates the new pixel/x value in order to send it to Screen/x:
```
;pixel/x LDA2 ( load pixel/x into the stack )
#0001 ADD2 ( increment )
DUP2 ( duplicate result )
.Screen/x DEO2 ( set as screen/x )
;pixel/x STA2 ( and store the result into pixel/x )
BRK
@pixel [ &x $2 &y $2 ]
```
we could have achieved the same result by storing the result first, then re-loading it and sent it as an output.
here we can see how a DUP2 can make that operation easier, as long as we keep a mental (or tangible!) model of what is happening on the stack.
## variables in the zero-page
variables with absolute address work well for cases where we want to be able to access their contents from any part of our program (i.e. "global variables").
however, uxn has a better mechanism for those cases: the zero page!
as you may recall, the zero page consists of the first 256 addresses of program memory. a program starts at address 0100, that is the next address after the zero page.
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.
the only caveat to keep in mind is that in order to initialize these variables we need to do it during runtime.
### labels
labels for the zero page would work the same as before; we only need to specify that they are in the zero page with an absolute pad:
```
|0000 ( zero page )
@pixel [ &x $2 &y $2 ]
```
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:
* LDZ: load and push down into the stack the value at the given zero page address ( address -- value )
* STZ: store into the given zero page address the value at the top of the stack ( value address -- )
in these instructions, the address will always be one byte long.
in the short mode, LDZ2 will load a short from the given address, and STZ2 will store a short into the given address.
### examples
the following example consists in the same line that grows, but now using the zero page to store the x and y coordinates of the pixel instead of the screen x and y ports.
in this case the program is longer, but it could be seen as a nice template for having other lines behaving in different ways:
```
( hello-line.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 ]
( zero page )
|0000
@pixel [ &x $2 &y $2 ]
( init )
|0100
( set system colors )
#2ce9 .System/r DEO2
#01c0 .System/g DEO2
#2ce5 .System/b DEO2
( set initial x,y coordinates )
#0008 .pixel/x STZ2
#0008 .pixel/y STZ2
( set screen vector )
;on-frame .Screen/vector DEO2
BRK
@on-frame ( -> )
( load x,y coordinates from zero page and send to screen )
.pixel/x LDZ2 .Screen/x DEO2
.pixel/y LDZ2 .Screen/y DEO2
( draw a pixel in the bg with color 1 )
#01 .Screen/pixel DEO
( increment pixel/x )
.pixel/x LDZ2 #0001 ADD2 .pixel/x STZ2
BRK
```
as an example to practice with the stack, note that the following instructions would also increment .pixel/x, but using its address only once:
```
.pixel/x DUP LDZ2 #0001 ADD2 ROT STZ2
```
i recommend you to follow how the stack contents change in each of the steps :)
this line of code contains the same amount of bytes than the previous one.
a possible disadvantage is that it might be less readable. and a possible advantage is that it could get converted to a macro:
```
( increment a short from the zero-page )
%ZP-INC2 { DUP LDZ2 #0001 ADD2 ROT STZ2 } ( zp-address -- )
```
## variables in relative addresses
another possibility that we have in uxn that might be more appropriate for "local variables", consists in using relative addresses.
similar to variables in the zero page, in order to address these variables we only need one byte.
however, as these addresses are given as relative and signed offsets, they can only be reached if they are within the 256 bytes surrounding the instruction that loads or stores them.
### instructions: LDR, STR
the instructions for working in this way are:
* 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 -- )
similar to LDZ and STZ, in these instructions the address will always be one byte long.
in the short mode, LDR2 will load a short from the given address, and STR2 will store a short into the given address.
### examples
the following is the on-frame subroutine that draws the growing line, but storing the pixel coordinates in a "local" variable accessed via LDR and STR.
```
@on-frame ( -> )
( load x,y coordinates from zero page and send to screen )
,pixel/x LDR2 .Screen/x DEO2
,pixel/y LDR2 .Screen/y DEO2
( draw a pixel in the bg with color 1 )
#01 .Screen/pixel DEO
( increment pixel/x )
,pixel/x LDR2 #0001 ADD2 ,pixel/x STR2
BRK
@pixel [ &x $2 &y $2 ]
```
note the use of the comma (,) rune to indicate that it's a relative address; uxnasm calculates the required offset assuming it will be used in the next instruction.
in this case we can't really duplicate that offset as we did previously with the zero-page address, because it is specific to the position in the code it was written.
if we declared these variables as sublabels of on-frame, the code would look as follows:
```
@on-frame ( -> )
( load x,y coordinates from zero page and send to screen )
,&pixel-x LDR2 .Screen/x DEO2
,&pixel-y LDR2 .Screen/y DEO2
( draw a pixel in the bg with color 1 )
#01 .Screen/pixel DEO
( increment pixel/x )
,&pixel-x LDR2 #0001 ADD2 ,&pixel-x STR2
( .pixel/x DUP LDR2 #0001 ADD2 ROT STZ2 )
BRK
( on-frame local variables )
&pixel-x $2 &pixel-y $2
```
note that in this case, the comma (,) rune is accompanied by the sublabel (&) rune.
the use of this kind of variables will make more sense in day 5 of the tutorial :)
# sprite change of position
the use of "variables" will help us now in discussing three different ways of animating a sprite:
* autonomous change of position
* interactive change of position (with keyboard)
* autonomous change of drawn tile
we will review them separately in order to keep the examples simple and readable. it will be up to you to combine and/or improve upon them :)
## autonomous change of position
we discussed already how to have uxn change the position of a pixel in the screen, leaving a trail.
changing that program in order to draw an 8x8 sprite instead would be relatively straightforward, and you may have tried it already: we would need to use Screen/sprite instead of Screen/pixel, with an appropriate mode byte, and we would need to set the address of our sprite data in Screen/addr.
that would result in a sprite that moves and that also leaves a trail.
how can we avoid leaving that trail?
a possible way of achieving it would be by following this order of operations inside the on-frame subroutine:
* clear sprite
* change position
* draw sprite
this allows us to clear the sprite from its position in the previous frame, update its coordinates to a new position, and then draw it there.
### example code
the following program illustrates the previous points, having our square from day 2 travel from left to right in the middle of our screen.
it combines several things that we have covered in the past few days!
```
( hello-animated-sprite.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 ]
( macros/constants )
%HALF2 { #01 SFT2 } ( shift one bit to the right ) ( short -- short/2 )
%color-clear { #40 } ( clear 1bpp sprite from fg )
%color-2 { #4a } ( draw 1bpp sprite with color 2 and transparency )
( zero page )
|0000
@sprite [ &pos-x $2 &pos-y $2 ]
( init )
|0100
( set system colors )
#2ce9 .System/r DEO2
#01c0 .System/g DEO2
#2ce5 .System/b DEO2
( set screen/y to half of screen minus 4 )
.Screen/height DEI2 HALF2 #0004 SUB2 .Screen/y DEO2
( set sprite address )
;square .Screen/addr DEO2
( set screen vector )
;on-frame .Screen/vector DEO2
BRK
@on-frame ( -> )
( 1: clear sprite )
( clear sprite from the fg )
color-clear .Screen/sprite DEO
( 2: change position )
( increment sprite/pos-x )
.sprite/pos-x LDZ2 #0001 ADD2 .sprite/pos-x STZ2
( 3 : draw sprite )
( load x coordinate from zero page and send to screen )
.sprite/pos-x LDZ2 .Screen/x DEO2
( draw sprite in fg with color 2 and transparency )
color-2 .Screen/sprite DEO
BRK
( sprite data )
@square ff81 8181 8181 81ff
```
note that on one hand there are some things that could be optimized in order to make our program smaller, and that on the other there are some things that could be useful but that were left out, like the initial value of the x coordinate, or the use of the y coordinate.
regarding optimization, and as an example, section 2 and the first part of section 3 of on-frame could have been written as follows:
```
( 2: change position )
( increment sprite/pos-x )
.sprite/pos-x LDZ2 #0001 ADD2
DUP2 ( duplicate result )
.sprite/pos-x STZ2 ( store the first copy of the result )
( 3 : draw sprite )
( use x coordinate from the stack and send to screen )
.Screen/x DEO2
```
as always, it is up to us how we want to navigate between efficient code and readability :)
here are some questions for you to ponder and try:
* how would you make the sprite move faster?
* and how would you make it move slower?
## interactive change of position
when we use the controller vector, we are acting upon a change in the button(s) or key(s) that were pressed or released. this can be very useful for some applications.
but, how can we deal with doing a continuous action when a key is kept pressed?
in some operating systems, if we keep a key pressed, it fires the controller vector several times.
however, this repetition might not allow for a smooth movement like what we can get if we check for the state of the controller inside the on-frame subroutine!
the following program allows us to control the horizontal position of our square using the arrow keys.
note the similarities between the previous program, and what we covered on day 3!
```
( hello-moving-sprite.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 ]
|80 @Controller [ &vector $2 &button $1 &key $1 ]
( macros/constants )
%HALF2 { #01 SFT2 } ( shift one bit to the right ) ( short -- short/2 )
%color-clear { #40 } ( clear 1bpp sprite from fg )
%color-2 { #4a } ( draw 1bpp sprite with color 2 and transparency )
( zero page )
|0000
@sprite [ &pos-x $2 &pos-y $2 ]
( init )
|0100
( set system colors )
#2ce9 .System/r DEO2
#01c0 .System/g DEO2
#2ce5 .System/b DEO2
( set screen/y to half of screen minus 4 )
.Screen/height DEI2 HALF2 #0004 SUB2 .Screen/y DEO2
( set sprite address )
;square .Screen/addr DEO2
( set screen vector )
;on-frame .Screen/vector DEO2
BRK
@on-frame ( -> )
( 1: clear sprite )
( clear sprite from the fg )
color-clear .Screen/sprite DEO
( 2: change position with arrow keys )
&check-arrows
.Controller/button DEI
#40 AND ( isolate bit 6, corresponding to Left )
,&left JCN ( jump if not 0 )
.Controller/button DEI
#80 AND ( isolate bit 7, corresponding to Right )
,&right JCN ( jump if not 0 )
( if none of those keys were pressed, draw without changes )
,&draw JMP
&left
( decrement sprite/pos-x )
.sprite/pos-x LDZ2 #0001 SUB2 .sprite/pos-x STZ2
,&draw JMP
&right
( increment sprite/pos-x )
.sprite/pos-x LDZ2 #0001 ADD2 .sprite/pos-x STZ2
( 3 : draw sprite )
&draw
( load x coordinate from zero page and send to screen )
.sprite/pos-x LDZ2 .Screen/x DEO2
( draw sprite in fg with color 2 and transparency )
color-2 .Screen/sprite DEO
BRK
( sprite data )
@square ff81 8181 8181 81ff
```
i invite you to adapt the code so that you can control the sprite in the four cardinal directions!
## moving within limits
as you may have noticed, these two previous programs allow our sprite to go outside the screen.
if we wanted to avoid that, one way we could do it would be by adding (more) conditionals.
### conditional limits
for example, instead of having an unconditional increment in the x coordinate:
```
( increment sprite/pos-x )
.sprite/pos-x LDZ2 #0001 ADD2 .sprite/pos-x STZ2
```
we could check first if it has reached a specific amount, like the width of the screen, and not increment if that's the case:
```
.sprite/pos-x LDZ2 ( load x )
.Screen/width DEI2 #0008 SUB2 ( get screen width minus 8 )
EQU2 ( is x equal to screen width - 8 ? )
,&continue JCN
&increment
( increment sprite/pos-x )
.sprite/pos-x LDZ2 #0001 ADD2 .sprite/pos-x STZ2
&continue
```
### modulo
another possibility could be applying a modulo operation to our changed coordinates so that they always stay within the limits, returning to the left when crossing over the right, and viceversa.
a possible set of modulo macros could be:
```
%MOD { DUP2 DIV MUL SUB } ( a b -- a%b )
%MOD2 { OVR2 OVR2 DIV2 MUL2 SUB2 } ( a b -- a%b )
```
(there's a more optimized set but we'll discuss it later :)
we can apply those macros after incrementing or decrementing. for example:
```
( increment sprite/pos-x )
.sprite/pos-x LDZ2 #0001 ADD2
.Screen/width DEI2 MOD2 ( apply modulo screen width )
.sprite/pos-x STZ2 ( store result )
```
# sprite frames animation
another strategy for animation would consist in changing the sprite that is drawn at a specific position.
you could have a sequence of sprites/frames and animate them by running them in sequence!
## the frames
for practical purposes i would recommend you to have a number of frames corresponding to a power of two, like 2, 4, 8, 16, 32, etc.
for example, the following is a sequence of eight 1bpp sprites corresponding to a diagonal line moving from the bottom right to the top left:
```
@animation
&frame0 00 00 00 00 00 00 01 03
&frame1 00 00 00 00 01 03 06 0c
&frame2 00 00 01 03 06 0c 18 30
&frame3 01 03 06 0c 18 30 60 c0
&frame4 03 06 0c 18 30 60 c0 80
&frame5 0c 18 30 60 c0 80 00 00
&frame6 30 60 c0 80 00 00 00 00
&frame7 c0 80 00 00 00 00 00 00
```
note that each frame consists in 8 bytes; that implies that there's an offset of 8 bytes between the addresses corresponding to each sublabel.
for example, the address of &frame1 would be 8 bytes more than the address of &frame0.
the frames you use could also be composed of 2bpp sprites. in that case, the offset between frames would be of 16 in decimal (10 in hexadecimal) bytes.
## frame count
in order to have an animation consisting of those frames we need to change the address of Screen/addr at specific intervals so that it points to a different sprite each time.
how can we know the sprite address that we should be using at each frame?
one way of achieving it is having a "global variable" in the zero page that counts the frames of the program. also, we would need to have that count constrained in a range corresponding to the amount of frames in our animation.
we know already how to do the first part!
in the zero page we declare the label for our framecount:
```
( zero page )
|0000
@framecount $1
```
and in the on-frame subroutine we increment it:
```
( increment framecount )
.framecount LDZ #01 ADD .framecount STZ
```
note that we are using a single byte to count, so it will go from 0 to 255 in a little bit more than 4 seconds, and then restart when overflowing its count.
for some applications it might be better to have a framecount in a short, that would count from 0 to 65535 and overflow in a little bit more than 18 minutes.
### fast modulo
in order to have that framecount constrained to a range corresponding to our number of frames, we can use a modulo operation.
when we have a number of frames corresponding to a power of two, as recommended above, we can use an "AND mask" to perform this modulo operation faster than if we were using the MOD macros suggested previously.
for example, if we have 8 frames numbered from 0 to 7, we can notice that those numbers only require three bits to be represented.
to build our AND mask, we set as 1 those three bits, and 0 the others:
```
0000 0111: 07
```
this AND mask will "let pass" the three least significant bits of another byte, and turn off the others.
in uxntal this process would look as follows:
```
.framecount LDZ ( load framecount )
#07 AND ( apply AND mask, corresponding to modulo 8 )
```
the result of the operation will be a count going repeatedly from 0 to 7.
we could define this fast modulo operation as a macro to make the code more readable:
```
%8MOD { #07 AND } ( byte -- byte%8 )
```
## pointer arithmetic
how can we use that count to select the sprite for the animation frame that we want to show?
we could use several conditional jumps, or we could use a more fun way that can be called pointer arithmetic :)
observe that the sublabel for the first frame (frame0) of our animation has the same address as the label for the whole animation. and, as we mentioned already, the next frame (frame1) starts 8 bytes afterwards.
the sublabel for each next frame is 8 bytes after the previous one.
or, another way to look at it:
* frame0 is 0 bytes after the animation label
* frame1 is 8 bytes after the animation label
* frame2 is 16 bytes after the animation label
* frame3 is 24 bytes after the animation label
* and so on
generalizing, frameN is (N times 8) bytes after the animation label!
this means that if we get the absolute address for the animation label, and we add (N times 8) bytes to it, we'll get the absolute address for frameN :)
this amount of bytes that separates each sublabel is called an offset.
### computing the offset
after applying the modulo 8 to our framecount, we can multiply it times 8 to get the offset with respect to the animation label:
```
.framecount LDZ ( load framecount )
8MOD ( apply modulo 8 to obtain sequence between 0 and 7 )
#08 MUL ( multiply times 8 to get the offset )
```
### from byte to short
note that so far we have been working with bytes, and everything has been fine.
however, absolute addresses are actually shorts!
we need to convert our result to a short so that we can add it to the address of the animation data.
one way of doing it is with this macro that adds a 00 before the top element in the stack:
```
%TO-SHORT { #00 SWP } ( byte -- short )
```
our code would look as follows:
```
.framecount LDZ ( load framecount )
8MOD ( apply modulo 8 to obtain sequence between 0 and 7 )
#08 MUL ( multiply times 8 to get the offset )
TO-SHORT ( convert to short )
```
another way, less clear but kind of fun (and slightly shorter in program memory), would consist in pushing the 00 before anything else happens:
```
#00 ( push high byte of the offset )
.framecount LDZ ( load framecount )
8MOD ( apply modulo 8 to obtain sequence between 0 and 7 )
#08 MUL ( multiply times 8 to get the offset )
```
### adding the offset
adding this offset to the address of our animation is simple:
```
.framecount LDZ ( load framecount )
8MOD ( apply modulo 8 to obtain sequence between 0 and 7 )
#08 MUL ( multiply times 8 to get the offset )
TO-SHORT ( convert to short )
;animation ( get animation address )
ADD2 ( add offset to address )
```
and then we could just send that to the Screen/addr port:
```
.Screen/addr DEO2 ( set computed address )
```
## the complete program
the program that does all this would look as follows.
note that it uses a sequence similar to the previous programs:
* increment framecount
* clear sprite
* calculate sprite address
* draw sprite
the clear sprite section is not actually needed in this case because of the colors that are used, but it would be when using colors with transparency in them :)
```
( hello-animation.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 ]
( macros/constants )
%HALF2 { #01 SFT2 } ( shift one bit to the right ) ( short -- short/2 )
%8MOD { #07 AND } ( byte -- byte%8 )
%TO-SHORT { #00 SWP } ( byte -- short )
%color-clear { #40 } ( clear 1bpp sprite from fg )
%color-2-3 { #4e } ( draw 1bpp sprite with color 2 and 3 )
( zero page )
|0000
@framecount $1
( init )
|0100
( set system colors )
#2ce9 .System/r DEO2
#01c0 .System/g DEO2
#2ce5 .System/b DEO2
( set screen/x and y to half of screen minus 4 )
.Screen/width DEI2 HALF2 #0004 SUB2 .Screen/x DEO2
.Screen/height DEI2 HALF2 #0004 SUB2 .Screen/y DEO2
( set sprite address )
;animation .Screen/addr DEO2
( set screen vector )
;on-frame .Screen/vector DEO2
BRK
@on-frame ( -> )
( 0: increment framecount )
.framecount LDZ #01 ADD .framecount STZ
( 1: clear sprite )
( clear sprite from the fg )
color-clear .Screen/sprite DEO
( 2: update sprite address )
.framecount LDZ ( load framecount )
8MOD ( apply modulo 8 to obtain sequence between 0 and 7 )
#08 MUL ( multiply times 8 to get the offset )
TO-SHORT ( convert to short )
;animation ( get animation address )
ADD2 ( add offset to address )
.Screen/addr DEO2 ( set computed address )
( draw sprite in fg with color 2 and 3 )
color-2-3 .Screen/sprite DEO
BRK
( sprite data )
@animation
&frame0 00 00 00 00 00 00 01 03
&frame1 00 00 00 00 01 03 06 0c
&frame2 00 00 01 03 06 0c 18 30
&frame3 01 03 06 0c 18 30 60 c0
&frame4 03 06 0c 18 30 60 c0 80
&frame5 0c 18 30 60 c0 80 00 00
&frame6 30 60 c0 80 00 00 00 00
&frame7 c0 80 00 00 00 00 00 00
```
i invite you to draw the tile multiple times with different flipping modes, in order to create more interesting animations!
or, better yet, design and use your own sprites!
## slow down!
until now, everything we have been doing has happened at 60 frames per second, that may be too fast for some applications!
fortunately, we can use some simple arithmetic with our framecount in order to slow down its effects.
for example, if we want to update our frames at half that speed (30 frames per second), we can divide over two the value of framecount before applying the modulo.
as you may remember this division can be done with SFT in the case of powers of two, or with DIV for any other case.
```
%HALF { #01 SFT } ( byte -- byte/2 )
%QUARTER { #02 SFT } ( byte -- byte/4 )
%EIGHTH { #03 SFT } ( byte -- byte/8 )
```
we can use these macros to divide the frequency in our code:
```
( 2: update sprite address )
.framecount LDZ ( load framecount )
QUARTER ( divide over 4 the frequency )
8MOD ( apply modulo 8 to obtain sequence between 0 and 7 )
#08 MUL ( multiply times 8 to get the offset )
TO-SHORT ( convert to short )
;animation ( get animation address )
ADD2 ( add offset to address )
.Screen/addr DEO2 ( set computed address )
```
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.
this might also happen if you have an animation consisting of a number of frames that is not a power of 2, and you use a normal MOD operation to calculate the offset to the corresponding frame.
the easiest workaround for these issues would be to use a short-sized framecount that would only cause those overflow glitches at approximately every 18 minutes.
you'd have to adapt the program to work with that size of framecount - nice exercise, i feel-think!
# instructions of day 4
these are all the uxntal instructions that we discussed today!
* LDA: load and push down into the stack the value at the given absolute address ( address -- value )
* STA: store into the given absolute address the value at the top of the stack ( value address -- )
* LDZ: load and push down into the stack the value at the given zero page address ( address -- value )
* STZ: store into the given zero page address the value at the top of the stack ( value address -- )
* 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 -- )
the addresses for LDA and STA are always shorts, while the addresses for the other instructions are always one byte.
in short mode, these instructions load or store shorts from or into memory.
# coming soon: day 5
in the next section of the {uxn tutorial} we'll discuss how to create more subroutines in uxntal to have more organized and modular programs.
this will lead us to talk about the return stack, and the return mode in uxn instructions: we'll finally cover them all!
we will also talk about repetitive structures (i.e. loops), and we'll discuss the varvara mouse device to explore other types of interactions!
meanwhile, i invite you to keep exploring, to share your findings, and to also take a break!
stay tuned!
# support
if you found this tutorial to be helpful, consider sharing it and giving it your {support} :)