minor corrections day 3

This commit is contained in:
sejo 2021-12-17 19:00:34 -06:00
parent 8bb981c40c
commit 2bbbf80c71
2 changed files with 113 additions and 78 deletions

View File

@ -129,16 +129,17 @@ you can find a more detailed reference of all the opcodes in the uxntal opcode m
## 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 )
* EQU: push 01 down into the stack if the top two elements of the stack are equal, or push 00 otherwise ( a b -- a==b )
* NEQ: push 01 down into the stack if the top two elements of the stack are not equal, or push 00 otherwise ( a b -- a!=b )
* GTH: push 01 down into the stack if the first element is greater than the second, or push 00 otherwise ( a b -- a>b )
* LTH: push 01 down into the stack if the first element is less than the second, or push 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 -- )
* JCN: conditional jump: take an address and a value from the stack, and if the value is not 00, jump to the address; 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 )

View File

@ -38,51 +38,57 @@ numbering the bits from right to left, and from 0 to 7, the corresponding keys (
+ <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 in this allows us to press and read many of these keys at the same time.
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
the key byte stores the ascii code of the keyboard key that is being pressed at the moment.
the difference between the 'key' byte and the 'button' byte can be confusing, especially when running varvara from uxnemu where the buttons are in the same place as the keys.
a possible way to remember might be to think of the 'button' byte as referring to a gamepad controller.
## the controller vector
a vector refers to an address in main memory where uxn is assigned to jump to when a specific event happens.
in the context of uxn programming, a vector refers to an address in main memory where uxn is assigned to jump to when a specific event happens.
in the case of the controller vector, this specific event consists in whenever a key is pressed or released.
the following line of code would assign the absolute address of the label on-controller to the controller vector:
in other words: uxn will jump to the address assigned as the controller vector, whenever a key is pressed or released.
the following line of code would assign that vector, using the absolute address of the label on-controller:
```
;on-controller .Controller/vector DEO2
```
uxn will "jump" to that address whenever a key is pressed or released.
let's see next how would that work!
let's see next how that would work!
# control flow: vector subroutines
so far our uxntal programs have followed a linear flow: they start at address 0100, and they end at the first BRK instruction that is found.
we can think of these programs as setup routines: they setup the system colors, they might draw or print some things, and then they leave uxn "waiting". what would uxn be waiting for?
we can think of these programs as setup routines: they setup the system colors, they might draw or print some things, and then they leave uxn waiting. what would uxn be waiting for?
yes, an option would be: waiting for keyboard input!
we will start organizing our uxntal programs in terms of subroutines that correspond to different vectors.
we will start organizing our uxntal programs in terms of subroutines that correspond to different vectors.
each of these subroutines will finish with the BRK instruction, so that they can return uxn to the waiting state.
each of these subroutines will end with the BRK instruction, so that they can make uxn return to the waiting state.
## controller vector subroutine
for example, the following program takes the sprite drawing we tried in the previous section, but has it ocurring only when a key is pressed:
to illustrate that behavior, let's read the following program.
it uses the sprite drawing procedure we tried in the previous day, but has it happening only when a key is pressed. in the beginning, the screen is empty, and when we press a key a square is drawn:
```
( hello-keyboard.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 ]
|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 ]
( main program )
|0100
@ -113,9 +119,13 @@ BRK
@square ff81 8181 8181 81ff
```
how do we take different actions depending on the key that was pressed?
nice, isn't it?
first of all, we have to let our program know which key was pressed. in order to achieve that, let's take a look at some uxntal instructions that will help us!
now, how can we take different actions depending on the key that was pressed?
first of all, we have to let our program know which key was pressed so that it can act accordingly.
in order to achieve that, let's take a look at some new uxntal instructions!
# comparison and logic instructions
@ -123,15 +133,17 @@ first of all, we have to let our program know which key was pressed. in order to
uxntal has four instructions for comparing the top two elements from the stack:
* 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 )
* EQU: push 01 down into the stack if the top two elements of the stack are equal, or push 00 otherwise ( a b -- a==b )
* NEQ: push 01 down into the stack if the top two elements of the stack are not equal, or push 00 otherwise ( a b -- a!=b )
* GTH: push 01 down into the stack if the first element is greater than the second, or push 00 otherwise ( a b -- a>b )
* LTH: push 01 down into the stack if the first element is less than the second, or push 00 otherwise ( a b -- a>b )
for example, the following code will read the value of the controller key, and push into the stack a flag corresponding to it being equal to character 'a':
we can think of the results pushed by these instructions as boolean flags: they are 01 if the comparison was true, and they are 00 if it was false.
here's a small example. the following code will read the value of the controller key, and push into the stack a flag corresponding to it being equal to character 'a':
```
.Controller/key DEI ( read key )
.Controller/key DEI ( read key and push it down into the stack )
LIT 'a ( push ascii code of character 'a' )
EQU ( compare both bytes and push 01 if they are the same, 00 if not )
```
@ -142,7 +154,7 @@ EQU2, NEQ2, GTH2 and LTH2 will work in the same way, but comparing shorts instea
uxntal has three bitwise logic instructions.
they can work as logic operators that have as operands the results given by the comparison instructions we discussed above:
they can work as logic operators that use as operands the flags given by the comparison instructions we discussed above:
* 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 )
@ -152,14 +164,14 @@ 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 answer "true", and 00 to answer "false":
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:
```
.Controller/key DEI ( read key )
.Controller/key DEI ( read key and push into the stack )
#2f GTH ( is it greater than 2f? push flag into the stack )
.Controller/key DEI ( read key )
.Controller/key DEI ( read key and push into the stack )
#3a LTH ( is it less than 3a? push flag into the stack )
AND ( apply an AND to the flags in the stack, and push the result in the stack )
AND ( apply an AND to the flags in the stack, and push the result into the stack )
```
that the instruction is bitwise means that it applies the AND operation to each of the bits of the operands.
@ -182,19 +194,21 @@ AND 0000 0000 ( false )
0000 0000 ( false )
```
as these flags only use the least significant bit (the rightmost bit) to encode their value, a bitwise AND is equivalent to a conventional logic AND.
### OR
the following code will push a flag down into the stack if the key byte is either '1' or 'a':
```
.Controller/key DEI ( read key )
.Controller/key DEI ( read key and push into the stack )
LIT '1 EQU ( is it '1'? push flag into the stack )
.Controller/key DEI ( read key )
.Controller/key DEI ( read key and push into the stack )
LIT 'a EQU ( is it 'a'? push flag into the stack )
ORA ( apply an OR to the flags in the stack, and push the result in the stack )
```
when any of the flags are true, the flag will be true:
when any or both of the flags are true, the flag will be true:
```
0000 0001 ( true )
@ -203,19 +217,23 @@ OR 0000 0000 ( false )
0000 0001 ( true )
```
only when both flags are false, the resulting flag will be false.
### EOR
an exclusive OR can be used to invert the value of a flag, by using a "mask" where the bit(s) that we'd like to invert are set as 1.
an exclusive-OR is a logical operation that has a result of true only when one or the other inputs are true. if both inputs are true, or if both inputs are false, the result is false.
based on that behavior, this instruction can be used to invert the value of a flag by using a special value where the bit(s) that we'd like to invert are set as 1. these kind of values are called masks.
for example, the folowing code will push a flag corresponding to the key being greater than or equal to 20, by calculating first if it's less than 20, and then inverting the result:
```
.Controller/key DEI ( read key )
.Controller/key DEI ( read key and push into the stack )
#20 LTH ( is it less than 20? push flag into the stack )
#01 EOR ( invert leftmost bit of the flag and push the result into the stack )
#01 EOR ( invert rightmost bit of the flag and push the result into the stack )
```
when the original flag is true, which means that the value is less than 20, the EOR will invert it and make it false: the value is NOT greater than or equal to 20:
when the original flag is true, which means that the key value is less than 20, the EOR will invert it and make it false: the value is NOT greater than or equal to 20:
```
0000 0001 ( true )
@ -224,6 +242,8 @@ EOR 0000 0001 ( mask )
0000 0000 ( false )
```
observe how because the two input bits are 1, the output bit is 0.
when the original flag is false, which means that the value is NOT less than 20, the EOR will invert it and make it true: the value is greater than or equal to 20:
```
@ -233,22 +253,26 @@ EOR 0000 0001 ( mask )
0000 0001 ( true )
```
note that the mask is the same, and the result is the opposite value of the flag.
# control flow: conditional jumps
ok, so how can we use these flags in order to have conditional expressions in our programs?
ok, so now our programs can identify and store in flags if a value (like the read keyboard key) is a specific value, or within some range.
let's introduce another set of new instructions to have uxn break its linear flow
how can we use these flags in order to have conditional behaviors in our programs, where different actions are taken depending on the results?
let's introduce another set of new instructions to have uxn break its linear flow!
## instructions for jumps
* 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 -- )
* JCN: conditional jump: take an address and a value from the stack, and if the value is not 00, jump to the address; otherwise continue with the next instruction ( value addr -- )
in the byte mode, the addresses that these instructions take are one byte long.
in byte mode, the addresses that these instructions use are one byte long.
these byte-long addresses are relative and signed: they indicate how many bytes have to be skipped in main memory from the current position of the program counter, either forward (positive) or backwards (negative). the range for these relative addresses is from -128 to 127 inclusive.
in short mode, the addresses that these instructions take are absolute (i.e. two-bytes long), but the value that JCN takes in order to decide is still a byte.
in short mode, the addresses that these instructions take are absolute (i.e. two-bytes long), but the value of the value that JCN takes in order to decide is still a byte.
## runes for addresses
@ -264,7 +288,7 @@ in the previous days we talked already about some of them; this is a recap of th
in order to define labels, we use:
* label definition: @label
* sublabel definition: &sublabel (this sublabel will be a "child" of the previously defined label)
* sublabel definition: &sublabel, where this sublabel will be a "child" of the previously defined label
and finally, to refer to labels within our uxntal code, we have the following cases:
@ -276,13 +300,16 @@ and finally, to refer to labels within our uxntal code, we have the following ca
let's join all of this together!
the following on-controller subroutine, illustrates the use of jumps, drawing our sprite only when the key that was pressed was '1':
the following on-controller subroutine illustrates the use of jumps, drawing our sprite only when the key that was pressed was '1':
```
@on-controller
.Controller/key DEI ( read key )
LIT '1 EQU ( is it '1'? )
,&draw-sprite JCN ( jump to draw-sprite if that's the case )
( jump to draw-sprite if that's the case )
,&draw-sprite JCN
,&end JMP ( otherwise, jump to the end )
&draw-sprite
@ -303,7 +330,9 @@ BRK
note the use of sublabels "inside" (after) on-controller.
also note how the expression ,&sublabel corresponds to the relative address that is needed in order to jump to that location in the code, either using JCN or JMP.
also note how the expression ,&sublabel corresponds to the relative address (,) that is needed in order to jump to that location in the code named with a local sublabel (&).
these relative, one-byte sized, addresses are used by JCN or JMP.
## conditional jumps
@ -350,19 +379,19 @@ the following code illustrates the use of many conditions: the color of the spri
BRK
```
note how the conditions are concatenated: whenever a flag is false, JCN allows uxn to continue with the next instruction in memory.
notice how the conditions are written one after another: whenever a flag is false, JCN allows uxn to continue with the next instruction in memory.
also note that this code is not optimized for size or speed, but for readability.
it would be up to you to, for example, perform arithmetic with the value of the key that was pressed in order to calculate the color to assign to the sprite :)
it would be up to you to, for example, perform arithmetic with the value of the key that was pressed in order to calculate the color to assign to the sprite - you could get inspiration from your PRINT-DIGIT macro from day 1!
# stack manipulation
so far we have been using the stack as a place to store operands of instructions and their results, but we haven't used yet the full potential of this data structure in a forth-like environment like uxn.
so far we have been using the stack as a place to store operands of instructions and their results, but we haven't used yet the full potential of this stack-based environment!
## stack instructions
uxntal has six instructions that act upon the elements in the stack:
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 )
@ -375,20 +404,20 @@ in short mode, POP2, DUP2, SWP2, NIP2, OVR2 and ROT2 perform the same actions bu
## examples
we'll be using these instructions in many different ways in the following days.
we'll be using these instructions in many different ways during the following days.
the following are some examples based on snippets of code that we discussed already.
keep in mind that using these instructions may contribute to a code that is hard to follow or read, so it will be always a good idea to use them within macros or to have comments in the code explaining what's happening.
keep in mind that using these instructions may contribute to a code that is hard to follow or read, so it will be always a good idea to use them within macros or to have comments in the code explaining what's happening :)
### ascii digit: duplicate and swap
we talked above about this piece of code, that pushes a flag that answers if the key that is pressed has an ascii code between 30 and 39, inclusive (i.e., if a byte has an ascii code corresponding to a decimal digit)
we discussed above this piece of code, that pushes a flag that answers if the key that is pressed has an ascii code between 30 and 39, inclusive (i.e., it calculates if a byte has an ascii code corresponding to a decimal digit)
```
.Controller/key DEI ( read key )
.Controller/key DEI ( read key and push into the stack )
#2f GTH ( is it greater than 2f? push flag into the stack )
.Controller/key DEI ( read key )
.Controller/key DEI ( read key and push into the stack )
#3a LTH ( is it less than 3a? push flag into the stack )
AND ( apply an AND to the flags in the stack, and push the result in the stack )
```
@ -399,13 +428,13 @@ instead of reading the key twice, we could do it once, and then use the DUP inst
.Controller/key DEI DUP ( read and duplicate key )
```
the stack after this would have:
the stack after these instructions would have two copies of the key value:
```
key key <- top
```
we can continue adding the first comparison:
then in our code we can continue adding the first comparison:
```
#2f GTH ( is it greater than 2f? push flag into the stack )
@ -425,13 +454,13 @@ how do we achieve that? that's right, using a SWP:
SWP ( put key at the top )
```
now the stack looks like:
now the stack looks like this:
```
flag1 key <- top
```
and 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 )
@ -456,9 +485,9 @@ AND ( apply an AND to the flags in the stack, and push the result in the stack )
the first code is assembled as 13 bytes, and this one is assembled as 12 bytes. maybe not too much of a difference on that front.
however, an advantage is that this new routine now needs its input pushed down into the stack only at its beginning.
however, a more meaningful advantage is that this new routine now needs its input pushed down into the stack only at the beginning.
in this case, the input is the key that is pressed, but we could easily have as an input any other value from the stack.
in the case we just discussed the input is the key that is pressed, but we could easily have as an input any other value from the stack.
this implies that we could write the routine as a macro:
@ -476,7 +505,7 @@ and use it with whatever byte we like:
### duplicates for conditionals
another instance where we repeated many reads of the keyboard key was when using the multiple conditionals above.
another instance above where we repeated many reads of the keyboard key was when using the multiple conditionals.
we could rewrite it using several DUPs and POPs:
@ -526,8 +555,11 @@ we could rewrite it using several DUPs and POPs:
BRK
```
can you tell why we need all those POPs?
can you tell why we need all those POPs?
hint: compare the final state of the stack with and without the POP instructions.
in the following days we'll see more uses and examples of stack manipulation!
# controller button
@ -543,7 +575,9 @@ meet the bitwise AND masks!
## AND mask
an AND mask will have bit(s) set as 1 in the position(s) where we want to keep the value of the original bit(s). in all the other positions, the original bits will be converted to 0.
an AND mask is a special value that we will use to keep or lose specific bits from another given value, like the controller button byte.
in our AND mask, we will set as 1 the bits in the positions where we want to keep the value of the input bits. the positions where the bits of the mask are 0 will be converted to 0 in the input.
for example, let's say we want to see if bit number 4, corresponding to the Up button, is on or off, regardless of the state of the other buttons.
@ -553,7 +587,7 @@ our AND mask will have a 1 in bit number 4 (from right to left, and starting at
0001 000: 10
```
what would happen if button A (Ctrl key), with its state in bit 0, is pressed, and nothing else?
what would happen if button A (Ctrl key), with its state encoded in bit 0, is pressed, and nothing else?
```
0000 0001 ( button )
@ -580,7 +614,7 @@ AND 0001 0000 ( mask )
0001 0000 ( result )
```
see how the mask allows us to effectively isolate the bit that matters to us, regardless of the sate of the other bits.
see how the mask allows us to effectively isolate the bit that matters to us, regardless of the state of the other bits.
applying this mask would be as simple as writing:
@ -592,7 +626,7 @@ applying this mask would be as simple as writing:
=> ./img/screenshot_uxn-draw-with-keyboard.png screenshot of a possible result of running the following program; it shows a trail drawn with filled or outlined squares.
the following uxntal program allows you to draw using the arrows keys and the Ctrl key.
the following uxntal program allows you to draw using the arrows keys and the Ctrl key (button A).
the arrows move the position of a sprite, and pressing Ctrl while moving it will draw it with the inverse colors in fill and stroke.
@ -601,9 +635,9 @@ note the use of AND masks, conditional jumps, and some stack operations!
```
( draw-with-keyboard.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 ]
|80 @Controller [ &vector $2 &button $1 &key $1 ]
|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 ]
|80 @Controller [ &vector $2 &button $1 &key $1 ]
( main program )
|0100
@ -691,7 +725,7 @@ here are some other ideas for you to practice with what we covered today!
note that for smooth interactive movement it might be better to use the screen vector that is called 60 times per second!
we'll cover it in depth in day 4 of the tutorial!
we'll cover that in depth in the next day of the tutorial!
# instructions of day 3
@ -699,10 +733,10 @@ these are all the uxntal instructions that we discussed today!
## comparison instructions
* 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&lt;b )
* EQU: push 01 down into the stack if the top two elements of the stack are equal, or push 00 otherwise ( a b -- a==b )
* NEQ: push 01 down into the stack if the top two elements of the stack are not equal, or push 00 otherwise ( a b -- a!=b )
* GTH: push 01 down into the stack if the first element is greater than the second, or push 00 otherwise ( a b -- a>b )
* LTH: push 01 down into the stack if the first element is less than the second, or push 00 otherwise ( a b -- a&lt;b )
## bitwise logic
@ -713,7 +747,7 @@ these are all the uxntal instructions that we discussed today!
## jumps
* 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 -- )
* JCN: conditional jump: take an address and a value from the stack, and if the value is not 00, jump to the address; otherwise continue with the next instruction ( value addr -- )
## stack