383 lines
13 KiB
Plaintext
383 lines
13 KiB
Plaintext
|
# uxn tutorial: day 3, the keyboard/controller
|
||
|
|
||
|
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.
|
||
|
|
||
|
we also talk about logic and stack manipulation instructions in uxntal.
|
||
|
|
||
|
# the controller device
|
||
|
|
||
|
the controller device allows us to read inputs from the keyboard in the case of uxnemu, and/or from the controller buttons in the case of uxn ports for handheld consoles.
|
||
|
|
||
|
the definition of its ports would look as follows in a typical uxntal program for the varvara computer:
|
||
|
|
||
|
```
|
||
|
|80 @Controller [ &vector $2 &button $1 &key $1 ]
|
||
|
```
|
||
|
|
||
|
## the button byte
|
||
|
|
||
|
the button byte encodes in each of its eight bits the state of eight different "buttons", based on the NES controller layout.
|
||
|
|
||
|
=> https://wiki.nesdev.com/w/index.php/Standard_controller standard NES controller
|
||
|
|
||
|
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
|
||
|
|
||
|
enconding the states in this 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 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 case of the controller vector, this specific event would be whenever a key is pressed or released.
|
||
|
|
||
|
for example, the following line of code would assign the absolute address of the label on-controller to the controller vector:
|
||
|
|
||
|
```
|
||
|
;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!
|
||
|
|
||
|
# 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?
|
||
|
|
||
|
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.
|
||
|
|
||
|
each of these subroutines will finish with the BRK instruction, so that they can return uxn 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:
|
||
|
|
||
|
```
|
||
|
( 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 &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
|
||
|
BRK
|
||
|
|
||
|
( run this code whenever a key is pressed or released )
|
||
|
@on-controller ( -> )
|
||
|
( set x,y coordinates )
|
||
|
#0008 .Screen/x DEO2
|
||
|
#0008 .Screen/y DEO2
|
||
|
|
||
|
( set sprite address )
|
||
|
;square .Screen/addr DEO2
|
||
|
|
||
|
( draw sprite in the background )
|
||
|
( using color 1 for the outline )
|
||
|
#21 .Screen/color DEO
|
||
|
BRK
|
||
|
|
||
|
( sprite )
|
||
|
@square ff81 8181 8181 81ff
|
||
|
```
|
||
|
|
||
|
how do 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 let's take a look at some uxntal instructions that will help us with that!
|
||
|
|
||
|
# comparison and logic instructions
|
||
|
|
||
|
## comparison instructions
|
||
|
|
||
|
uxntal has four instructions for comparing two bytes:
|
||
|
|
||
|
* 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 )
|
||
|
|
||
|
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':
|
||
|
|
||
|
```
|
||
|
.Controller/key DEI ( read key )
|
||
|
LIT 'a ( push ascii code of character 'a' )
|
||
|
EQU ( compare both bytes and push 01 if they are the same, 00 if not )
|
||
|
```
|
||
|
|
||
|
EQU2, NEQ2, GTH2 and LTH2 will work in the same way, but comparing shorts instead of bytes.
|
||
|
|
||
|
## logic instructions
|
||
|
|
||
|
uxntal has three bitwise logic instructions, that can work as logic operators with 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 )
|
||
|
* EOR: perform a bitwise exclusive-OR with the top two elements of the stack, and push down the result ( a b -- a^b )
|
||
|
|
||
|
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":
|
||
|
|
||
|
```
|
||
|
.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 )
|
||
|
```
|
||
|
|
||
|
that the instruction is bitwise means that it applies the AND operation to each of the bits of the operands.
|
||
|
|
||
|
if both flags were "true":
|
||
|
|
||
|
```
|
||
|
0000 0001 ( true )
|
||
|
AND 0000 0001 ( true )
|
||
|
----------
|
||
|
0000 0001 ( true )
|
||
|
```
|
||
|
|
||
|
if any (or both) of the flags were "false":
|
||
|
|
||
|
```
|
||
|
0000 0001 ( true )
|
||
|
AND 0000 0000 ( false )
|
||
|
----------
|
||
|
0000 0000 ( false )
|
||
|
```
|
||
|
|
||
|
### 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 )
|
||
|
LIT '1 EQU ( is it '1'? push flag into the stack )
|
||
|
.Controller/key DEI ( ready key )
|
||
|
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:
|
||
|
|
||
|
```
|
||
|
0000 0001 ( true )
|
||
|
OR 0000 0000 ( false )
|
||
|
----------
|
||
|
0000 0001 ( true )
|
||
|
```
|
||
|
|
||
|
### 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.
|
||
|
|
||
|
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 )
|
||
|
#20 LTH ( is it less than 20? push flag into the stack )
|
||
|
#01 EOR ( invert 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:
|
||
|
|
||
|
```
|
||
|
0000 0001 ( true )
|
||
|
EOR 0000 0001 ( mask )
|
||
|
----------
|
||
|
0000 0000 ( false )
|
||
|
```
|
||
|
|
||
|
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:
|
||
|
|
||
|
```
|
||
|
0000 0000 ( false )
|
||
|
EOR 0000 0001 ( mask )
|
||
|
----------
|
||
|
0000 0001 ( true )
|
||
|
```
|
||
|
|
||
|
# control flow: conditional jumps
|
||
|
|
||
|
ok, so how can we use these flags in order to have conditional expressions in our programs?
|
||
|
|
||
|
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 -- )
|
||
|
|
||
|
in the byte mode, the addresses that these instructions take are one byte long.
|
||
|
|
||
|
these addresses are relative and signed: they indicate how many bytes have to be skipped, 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.
|
||
|
|
||
|
## runes for addresses
|
||
|
|
||
|
there are several runes that refer to addresses and labels. uxnasm reads them and converts them to the corresponding binary values.
|
||
|
|
||
|
in the previous days we talked already about some of them; this is a recap of those, and an introduction of the new ones:
|
||
|
|
||
|
* literal address in zero page: .label (one byte)
|
||
|
* literal address in main memory: ;label (one short)
|
||
|
* literal relative address in main memory: ,label (one byte)
|
||
|
* raw address in main memory: :label (one short)
|
||
|
|
||
|
in order to define labels, we use:
|
||
|
|
||
|
* label definition: @label
|
||
|
* sublabel definition: &sublabel
|
||
|
|
||
|
and finally, to refer to labels within our uxntal code, we have the following cases:
|
||
|
|
||
|
* for a main label: use the label name
|
||
|
* for a sublabel: use label/sublabel
|
||
|
* for a local sublabel: use &sublabel
|
||
|
|
||
|
## conditional jump
|
||
|
|
||
|
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':
|
||
|
|
||
|
```
|
||
|
@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 )
|
||
|
,&end JMP ( otherwise, jump to the end )
|
||
|
|
||
|
&draw-sprite
|
||
|
( set x,y coordinates )
|
||
|
#0008 .Screen/x DEO2
|
||
|
#0008 .Screen/y DEO2
|
||
|
|
||
|
( set sprite address )
|
||
|
;square .Screen/addr DEO2
|
||
|
|
||
|
( draw sprite in the background )
|
||
|
( using color 1 for the outline )
|
||
|
#21 .Screen/color DEO
|
||
|
|
||
|
&end
|
||
|
BRK
|
||
|
```
|
||
|
|
||
|
note the use of sublabels "inside" 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.
|
||
|
|
||
|
## conditional jumps
|
||
|
|
||
|
the following code illustrates the use of many conditions: the color of the sprite changes accordingly if the keys 1, 2 or 3 are pressed.
|
||
|
|
||
|
```
|
||
|
@on-controller
|
||
|
( set x,y coordinates )
|
||
|
#0008 .Screen/x DEO2
|
||
|
#0008 .Screen/y DEO2
|
||
|
|
||
|
( set sprite address )
|
||
|
;square .Screen/addr DEO2
|
||
|
|
||
|
.Controller/key DEI LIT '1 EQU ( is the key '1'? )
|
||
|
,&color-1 JCN ( jump to color-1 if that's the case )
|
||
|
|
||
|
.Controller/key DEI LIT '2 EQU ( is the key '2'? )
|
||
|
,&color-2 JCN ( jump to color-2 if that's the case )
|
||
|
|
||
|
.Controller/key DEI LIT '3 EQU ( is the key '3'? )
|
||
|
,&color-3 JCN ( jump to color-3 if that's the case )
|
||
|
|
||
|
( in any other case, finish )
|
||
|
BRK
|
||
|
|
||
|
&color-1
|
||
|
( draw sprite in the background )
|
||
|
( using color 1 for the outline )
|
||
|
#21 .Screen/color DEO
|
||
|
BRK
|
||
|
|
||
|
&color-2
|
||
|
( draw sprite in the background )
|
||
|
( using color 2 for the outline )
|
||
|
#22 .Screen/color DEO
|
||
|
BRK
|
||
|
|
||
|
&color-3
|
||
|
( draw sprite in the background )
|
||
|
( using color 3 for the outline )
|
||
|
#23 .Screen/color DEO
|
||
|
BRK
|
||
|
BRK
|
||
|
```
|
||
|
|
||
|
note how the conditions are concatenated: 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 :)
|
||
|
|
||
|
# stack manipulation
|
||
|
|
||
|
|
||
|
# controller button
|
||
|
|
||
|
* controller vector
|
||
|
* flow control: conditionals, relative and absolute jumps
|
||
|
* runes for addresses
|
||
|
* button and key
|
||
|
* bitwise masks
|
||
|
* practice: move/change sprite with keyboard
|
||
|
|
||
|
# instructions of day 3
|
||
|
|
||
|
## 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>b )
|
||
|
|
||
|
## bitwise logic
|
||
|
|
||
|
* 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 )
|
||
|
|
||
|
## 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 -- )
|