finish (?) day 3

This commit is contained in:
sejo 2021-07-31 14:03:53 -05:00
parent 31188a1949
commit 63d7431728
1 changed files with 371 additions and 17 deletions

View File

@ -1,6 +1,6 @@
# uxn tutorial: day 3, conditional jumpts and the keyboard/controller
# uxn tutorial: day 3, conditional jumps and the keyboard/controller
this is the third section of the <(uxn tutorial)>! ( currently in construction )
this is the third section of the {uxn tutorial}!
here we introduce the use of the controller device in the varvara uxn computer: this will allow us to add interactivity to our programs, and to start discussing control flow in uxntal.
@ -10,7 +10,7 @@ we also talk about logic and stack manipulation instructions in uxntal.
the controller device in the varvara computer allows us to read inputs from the keyboard and/or from controller buttons.
the definition of its ports would look as follows in a typical uxntal program for the varvara computer:
the definition of its ports would look as follows in a typical program:
```
|80 @Controller [ &vector $2 &button $1 &key $1 ]
@ -24,14 +24,19 @@ the button byte encodes in each of its eight bits the state of eight different "
numbering the bits from right to left, and from 0 to 7, the corresponding keys (and NES buttons) are:
* 0: Ctrl (button A)
* 1: Alt (button B)
* 2: Shift (Select button)
* 3: Esc (Start button)
* 4: Up
* 5: Down
* 6: Left
* 7: Right
& * 0: Ctrl (button A)
& * 1: Alt (button B)
& * 2: Shift (Select button)
& * 3: Esc (Start button)
& * 4: Up
& * 5: Down
& * 6: Left
& * 7: Right
+ <table>
+ <tr><th>bit 7</th><th>bit 6</th><th>bit 5</th><th>bit 4</th><th>bit 3</th><th>bit 2</th><th>bit 1</th><th>bit 0</th></tr>
+ <tr><td>Right</td><td>Left</td><td>Down</td><td>Up</td><td>Esc (Start)</td><td>Shift (Select)</td><td>Alt (B)</td><td>Ctrl (A)</td></tr>
+ </table>
enconding the states in this allows us to press and read many of these keys at the same time.
@ -116,7 +121,7 @@ first of all, we have to let our program know which key was pressed. in order to
## comparison instructions
uxntal has four instructions for comparing two bytes:
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 )
@ -135,7 +140,9 @@ EQU2, NEQ2, GTH2 and LTH2 will work in the same way, but comparing shorts instea
## logic instructions
uxntal has three bitwise logic instructions, that can work as logic operators that have as operands the results given by the comparison instructions we discussed above:
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:
* 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 )
@ -257,7 +264,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 correspond to the last previous label)
* sublabel definition: &sublabel (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:
@ -351,17 +358,344 @@ it would be up to you to, for example, perform arithmetic with the value of the
# 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.
## stack instructions
uxntal has five instructions that act upon the elements in the stack:
* 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 )
in short mode, POP2, DUP2, SWP2, OVR2 and ROT2 perform the same actions but using shorts instead of bytes.
## examples
we'll be using these instructions in many different ways in 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.
### 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)
```
.Controller/key DEI ( read key )
#2f GTH ( is it greater than 2f? push flag into the stack )
.Controller/key DEI ( ready key )
#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 )
```
instead of reading the key twice, we could do it once, and then use the DUP instruction to copy the value:
```
.Controller/key DEI DUP ( read and duplicate key )
```
the stack after this would have:
```
key key <- top
```
we can continue adding the first comparison:
```
#2f GTH ( is it greater than 2f? push flag into the stack )
```
after this, the stack would look like:
```
key flag1 <- top
```
in order to perform the second comparison, we need to have the key at the top, not the flag.
how do we achieve that? that's right, using a SWP:
```
SWP ( put key at the top )
```
now the stack looks like:
```
flag1 key <- top
```
and we can proceed with the comparison and the AND
```
#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 )
```
ending with a stack that has the result only:
```
result <- top
```
the complete code would read as:
```
.Controller/key DEI DUP ( read and duplicate key )
#2f GTH ( is it greater than 2f? push flag into the stack )
SWP ( put key at the top )
#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 )
```
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.
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.
this implies that we could write the routine as a macro:
```
%?ASCII-DIGIT { DUP #2f GTH SWP #3a LTH AND } ( byte -- flag )
```
and use it with whatever byte we like:
```
#30 ?ASCII-DIGIT ( pushes 01 down into the stack )
#20 ?ASCII-DIGIT ( pushes 00 down into the stack )
.Controller/key ?ASCII-DIGIT ( pushes a corresponding flag down into the stack )
```
### duplicates for conditionals
another instance where we repeated many reads of the keyboard key was when using the multiple conditionals above.
we could rewrite it using several DUPs and POPs:
```
@on-controller
( set x,y coordinates )
#0008 .Screen/x DEO2
#0008 .Screen/y DEO2
( set sprite address )
;square .Screen/addr DEO2
.Controller/key DEI ( read key )
DUP LIT '1 EQU ( is the key '1'? )
,&color-1 JCN ( jump to color-1 if that's the case )
DUP LIT '2 EQU ( is the key '2'? )
,&color-2 JCN ( jump to color-2 if that's the case )
DUP LIT '3 EQU ( is the key '3'? )
,&color-3 JCN ( jump to color-3 if that's the case )
( in any other case, finish )
POP
BRK
&color-1
( draw sprite in the background )
( using color 1 for the outline )
#21 .Screen/color DEO
POP
BRK
&color-2
( draw sprite in the background )
( using color 2 for the outline )
#22 .Screen/color DEO
POP
BRK
&color-3
( draw sprite in the background )
( using color 3 for the outline )
#23 .Screen/color DEO
POP
BRK
BRK
```
can you tell why we need all those POPs?
# controller button
* bitwise masks
the last thing we'll discuss today is the use of the controller button byte in the varvara computer.
# practice
as we mentioned already, the main difference here is that this byte holds the state of 8 buttons in each of its bits.
* practice: move/change sprite with keyboard
depending on our application, we might need to be able to allow for some of these buttons to be pressed at the same time.
in that case, how would we isolate each of the bits to check their state individually?
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.
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.
our AND mask will have a 1 in bit number 4 (from right to left, and starting at 0), and 0 elsewhere:
```
0001 000: 10
```
what would happen if button A (Ctrl key), with its state in bit 0, is pressed, and nothing else?
```
0000 0001 ( button )
AND 0001 0000 ( mask )
----------
0000 0000 ( result )
```
what happens if the Up button is pressed?
```
0001 0000 ( button )
AND 0001 0000 ( mask )
----------
0001 0000 ( result )
```
and if both Up and Ctrl are pressed?
```
0001 0001 ( button )
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.
applying this mask would be as simple as writing:
```
#10 AND ( apply 0001 000 mask )
```
## example: draw with arrows and Ctrl
=> ./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 arrows move the position of a sprite, and pressing Ctrl while moving it will draw it with the inverse colors in fill and stroke.
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 &color $1 ]
|80 @Controller [ &vector $2 &button $1 &key $1 ]
( main program )
|0100
( set system colors )
#2ce9 .System/r DEO2
#01c0 .System/g DEO2
#2ce5 .System/b DEO2
( assign controller vector )
;on-controller .Controller/vector DEO2
( set initial x,y coordinates )
#0008 .Screen/x DEO2
#0008 .Screen/y DEO2
( set sprite address )
;square .Screen/addr DEO2
BRK
@on-controller ( -> )
.Controller/button DEI DUP ( read and duplicate button byte )
#01 AND ( isolate bit 0, corresponding to Ctrl )
,&fill JCN ( if the bit is not 0, jump to fill, otherwise continue )
&outline
#21 .Screen/color DEO ( draw outline )
,&check-arrows JMP ( continue to check-arrows )
&fill
#24 .Screen/color DEO ( draw filled )
&check-arrows
( use button byte from the stack )
DUP #10 AND ( isolate bit 4, corresponding to Up )
,&up JCN ( jump if not 0 )
DUP #20 AND ( isolate bit 5, corresponding to Down )
,&down JCN ( jump if not 0 )
DUP #40 AND ( isolate bit 6, corresponding to Left )
,&left JCN ( jump if not 0 )
DUP #80 AND ( isolate bit 7, corresponding to Right )
,&right JCN ( jump if not 0 )
POP BRK
&up
.Screen/y DEI2 #0008 SUB2 .Screen/y DEO2 ( decrement y )
POP
BRK
&down
.Screen/y DEI2 #0008 ADD2 .Screen/y DEO2 ( increment y )
POP
BRK
&left
.Screen/x DEI2 #0008 SUB2 .Screen/x DEO2 ( decrement x )
POP
BRK
&right
.Screen/x DEI2 #0008 ADD2 .Screen/x DEO2 ( increment x )
POP
BRK
BRK
( sprite )
@square ff81 8181 8181 81ff
```
some possibilities for you to practice:
* modify the code so that it will also respond to you pressing more than one arrow at the same time.
* convert the increments and decrements of the coordinates to macros that take the address of the port as input, and perform an equivalent operation. both of these lines should work using the same macro:
```
.Screen/x INCREMENT
.Screen/y INCREMENT
```
remember that .Screen/x is a literal address in the zero page, i.e. it pushes a byte corresponding to the address of the Screen/x sublabel :)
# practice possibilities
here are some other ideas for you to practice with what we covered today!
* draw a virtual controller that shows which of its buttons, mapped to keyboard keys, are being pressed
* create some kind of typewriter that draws different symbols and moves the drawing cursor depending on the key that was pressed.
* draw a character that changes its state according to the key you pressed. maybe use multiple tiles to draw it?
* create a simple tic-tac-toe board for two players: one key draws a X, another draws a O, and the arrows allow you to choose the cell to draw.
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!
# instructions of day 3
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 )
@ -379,3 +713,23 @@ it would be up to you to, for example, perform arithmetic with the value of the
* 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 -- )
## stack
* 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 )
# coming soon: 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.
meanwhile, i invite you to keep exploring 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} :)