Day 023: INKEY$
#1
INKEY$ is one of several methods to communicate with the keyboard. Others are _KEYHIT, _KEYDOWN, INP(), PEEK and POKE, and for Windows users, the function GetAsyncKeyState%.

So why is INKEY$ so much better than those other choices, why because I code with it, of course!

Well, my comedy bit aside, INKEY$ is comfortable for me because it has easily recognized key associations, by string representation of ASCII values.

Terminate loop with the Esc key example...
Code: (Select All)
DO
_LIMIT 30
LOOP UNTIL INKEY$ = CHR$(27) ' The string character for ASCII code 27.

Since INKEY$ functions as a string, unlike the other alternatives, we need recognize a string is true or false by being either filled or null.

Terminate loop with press almost any key...
Code: (Select All)
DO
_LIMIT 30
LOOP UNTIL LEN(INKEY$) ' Loop exits when INKEY$ is no longer null, (e.g. INKEY$ = "").

I find the best way to set up a keyboard poll routine is by assigning a variable to INKEY$...

Code: (Select All)
DO
    _LIMIT 30
    mykey$ = INKEY$
    IF LEN(mykey$) THEN
        SELECT CASE mykey$
            CASE CHR$(27)
                PRINT " INKEY$ CODE: CHR$(27)"
                EXIT DO ' Escape loop to end program snippet.
            CASE ELSE
                show_Values (mykey$)
        END SELECT
    END IF
LOOP

SUB show_Values (mykey$)
    SELECT CASE LEN(mykey$)
        CASE 1
            b = ASC(mykey$) ' ASC() converts a string character to a numeric value.
            a$ = ""
        CASE 2
            b = ASC(MID$(mykey$, 2, 1))
            a$ = "CHR$(0)" ' This is the nul character INKEY$ reports for 2 byte length key representation like the F1 - F12 keys.
    END SELECT
    b$ = LTRIM$(STR$(b)) ' This is how you convert a numeric variable to a string variable.
        PRINT " INKEY$ CODE: ";
        IF LEN(a$) THEN PRINT a$; " + ";
        PRINT "CHR$("; b$; ") "; "AKA: " + CHR$(34) + CHR$(b) + CHR$(34) + " ";
END SUB

"...press almost any key." WTH is that???

Well, INKEY$ does not register for certain keys like...

Shift
Ctrl
Alt
Fn
Windows key
PrtScr
NumLock
Capslock

These keys need to be referenced in some other either supportive method or via a different key recognition keyword like _KEYDOWN.

An "old school" method I love, and successfully lobbied for 15+ years ago, is the supportive method of using PEEK addresses.

This routine identifies if Alt, Ctrl, or Shift keys are held down while other keys are pressed...
Code: (Select All)
_CONTROLCHR OFF '  Allows us to key input combinations that would otherwise adversely affect screen output.
DO
    _LIMIT 30
    DEF SEG = 0
    IF PEEK(1047) MOD 16 = 1 OR PEEK(1047) MOD 16 = 2 THEN
        KeyCombos = 1 ' Shift
    ELSEIF PEEK(1047) MOD 16 = 3 OR PEEK(1047) MOD 16 = 4 THEN
        KeyCombos = 2 ' Ctrl
    ELSEIF PEEK(1047) MOD 16 = 5 OR PEEK(1047) MOD 16 = 6 THEN
        KeyCombos = 3 ' Ctrl+Shift
    ELSEIF PEEK(1047) MOD 16 = 7 OR PEEK(1047) MOD 16 = 8 THEN
        KeyCombos = 4 ' Alt
    ELSEIF PEEK(1047) MOD 16 = 9 OR PEEK(1047) MOD 16 = 10 THEN
        KeyCombos = 5 ' Shift+Alt
    ELSEIF PEEK(1047) MOD 16 = 12 THEN
        KeyCombos = 6 ' Ctrl+Alt
    ELSEIF PEEK(1047) MOD 16 = 14 THEN
        KeyCombos = 7 ' Shift+Ctrl+Alt
    ELSE
        KeyCombos = 0
    END IF
    DEF SEG

    mykey$ = INKEY$
    IF LEN(mykey$) OR KeyCombos THEN
        SELECT CASE mykey$
            CASE CHR$(27)
                PRINT " INKEY$ CODE: CHR$(27)"
                EXIT DO ' Escape loop to end program snippet.
        END SELECT
        show_Values mykey$, KeyCombos
    END IF
LOOP

SUB show_Values (mykey$, KeyCombos)
    STATIC OldKeyCombos
    IF KeyCombos = OldKeyCombos AND LEN(mykey$) = 0 THEN EXIT SUB ' Neat trick to only print once to screen while PEEK discovered keys are held down.
    DO ' Falx loop to avoid printing INKEY printing if only a non-INKEY$ is held down.
        SELECT CASE LEN(mykey$)
            CASE 0
                EXIT DO
            CASE 1
                b = ASC(mykey$) ' ASC() converts a string character to a numeric value.
                a$ = ""
            CASE 2
                b = ASC(MID$(mykey$, 2, 1))
                a$ = "CHR$(0)" ' This is the nul character INKEY$ reports for 2 byte length key representation like the F1 - F12 keys.
        END SELECT
        b$ = LTRIM$(STR$(b)) ' This is how you convert a numeric variable to a string variable.
        PRINT " INKEY$ CODE: ";
        IF LEN(a$) THEN PRINT a$; " + ";
        PRINT "CHR$("; b$; ") "; "AKA: " + CHR$(34) + CHR$(b) + CHR$(34) + " ";
        EXIT DO
    LOOP
    IF KeyCombos THEN
        SELECT CASE KeyCombos
            CASE 1
                PRINT " Shift Down";
            CASE 2
                PRINT " Crtl Down";
            CASE 3
                PRINT " Shift + Ctrl Down";
            CASE 4
                PRINT " Alt Down";
            CASE 5
                PRINT " Shift + Alt Down";
            CASE 6
                PRINT " Ctrl + Alt Down";
            CASE 7
                PRINT " Shift + Ctrl + Alt Down";
        END SELECT
        OldKeyCombos = KeyCombos
    END IF
    PRINT
END SUB

Cool, right? Well, not so fast there Sparky, there is a drawback. We can only read 3 key inputs at a time. Hold Shift + Ctrl + Alt then press "A" and just like I stated, the 4th input, "A" won't get printed. INP() won't help here either, and interestingly enough, although _KEYHIT and _KEYDOWN can handle most 3 key combinations, I noted some arrow combos and others were not registered when trying to get that technique to work. WinAPI can recognize some but not all 4+ multiple key presses. Fool around with some keys in this example if you have a Windows System. Hold Shift+Ctrl+Alt and try some arrow keys etc to see which ones show up for INKEY$. Some will, some won't, but those are all 4 key press events with this INKEY$ helper.

The only key I set to register in the alphabet is "A" so try Shift+Ctrl+Alt+A to see them all register. Oh, if you fooled with the code to try and get A+B+C+D to register, it would only register A+B+C.

Code: (Select All)
CONST VK_SHIFT = &H10 'SHIFT key
CONST VK_CONTROL = &H11 'CTRL key
CONST VK_MENU = &H12 'ALT key

DECLARE DYNAMIC LIBRARY "User32"
    FUNCTION GetAsyncKeyState% (BYVAL vkey AS LONG)
END DECLARE

DO
    _LIMIT 30
    a% = GetAsyncKeyState%(VK_SHIFT)
    b% = GetAsyncKeyState%(VK_CONTROL)
    c% = GetAsyncKeyState%(VK_MENU)
    d% = GetAsyncKeyState%(&H41)
    SELECT CASE a%
        CASE 0
        CASE ELSE
            PRINT a%
    END SELECT
    PRINT a%, b%, c%, d%, INKEY$
LOOP

So what else can we say about INKEY$? Well, it can be used in combination with other keyboard keywords. Maybe not good practice, but just saying it's workable.

INKEY$ stores key presses in a buffer. You can use _KEYCLEAR to clear that buffer. Some programs need this to stop users who press keys during a program pause from being used in the routine. SLEEP is actually a very simple example...

Code: (Select All)
SLEEP
IF LEN(INKEY$) THEN BEEP ' Will Beep
SLEEP
_KEYCLEAR
IF LEN(INKEY$) THEN BEEP ' Won't beep.

A bit more involved example of the buffer uses a delay to slow the typing output to the screen. With the buffer, Demo 1, the output catches up after you are done fast typing. In demo 2, _KEYCLEAR clears the buffer and the utput stops the moment the user stops fast typing.

Code: (Select All)
PRINT "Demo 1: Buffer will catch up to your typing. Press Es when ready for demo 2..": PRINT
LOCATE , , 1 ' Let's show the cursor for these typing demos...
' That third LOCATE parameter of 1 gives us the underline stype cursor.
' See the LOCATE Keyword of the Day for other cursor appearance options).
DO
    _LIMIT 30
    mykey$ = INKEY$
    IF LEN(mykey$) OR KeyCombos THEN
        SELECT CASE mykey$
            CASE CHR$(27)
                EXIT DO
            CASE ELSE
                IF LEN(mykey$) = 1 THEN
                    PRINT mykey$;
                    _DELAY .25
                END IF
        END SELECT
    END IF
LOOP
PRINT: PRINT: PRINT "Okay, demo two. The printing to the screen stops the moment you stop typing.": PRINT
DO
    _LIMIT 30
    mykey$ = INKEY$
    IF LEN(mykey$) OR KeyCombos THEN
        SELECT CASE mykey$
            CASE CHR$(27)
                EXIT DO
            CASE ELSE
                IF LEN(mykey$) = 1 THEN
                    PRINT mykey$;
                    _KEYCLEAR
                    _DELAY .25
                END IF
        END SELECT
    END IF
LOOP

On a final note, my beloved INKEY$, as stated previously, can't do everything. For instance say you want to use Ctrl + and Ctrl - to resize text, just like browsers. Well, we can't do that with INKEY$, but we can use _KEYHIT or Win32 API. An example of each is provided here: https://staging.qb64phoenix.com/showthread.php?tid=1230

Pete
Reply
#2
"INP(), PEEK and POKE", eh? "INKEY$" was always one of my favorite functions but with that keystroke buffer I considered a PITA. Without a buffer, however, say on the TRS-80 Color Computer, sometimes the computer was unresponsive thanks to the slow interpreter.

Note that porting to other BASIC's could be a headache, such as Freebasic developers' decision to change to CHR$(255) from CHR$(0) the first out of two characters returned by arrow key or such other key. The CHR$(255) + "k" (lowercase "K") combination then could be used to trap the "X" on the right-hand corner of a window, for a program compiled in GUI mode.

Despite attempts to phase out "INKEY$" this is a stronghold for beginners. A great many programs still use it and don't resort to "cheating" by looking at input ports and special memory areas. However, a game that has to look and act professionally such as an RPG requires extra strength interacting with HID. I have experienced this. I have to go look for the source code of a program that mimicked a good game I played on the Apple IIe. The response is a bit late because it uses "INKEY$" and also involved "_LIMIT" used inside a loop. This was already discussed sometime ago.

I had created another game which could have produced laughter if I shared it here, because it was supposed to convey the sense of speed. Some spaceship travelling "forward" through space either slowly or quickly, through "obstacles". But it doesn't do it by scaling "_DELAY" and it doesn't use "_LIMIT". The way I did it, I think would cause someone to say, "This game was done by a n00b!" Actually it's a work in progress (was done in 32-bit Linux with Freebasic).
Reply




Users browsing this thread: 1 Guest(s)