Task 10  Task 10: Gathering Input  Task 10


QB64 offeres a wide variety of gathering input from the users that will eventually play your games. Input can be gathered from the keyboard, joysticks, the mouse and even USB game pads. This task will cover the keyboard and mouse since those are the two primary means every computer user has at their direct disposal.

Up to this point the only way that has formally been shown to gather input is through the use of the INPUT statement. The problem with the INPUT statement is that you must wait for the user to press the ENTER key before the program will continue. This does not make for very exciting, action packed games now does it?


-- Keyboard Input --

- INPUT -

As discussed in Task 5 the INPUT statement is a simple way of getting information from the user through the keyboard. The INPUT statement allows the programmer to gather both string (text) and numeric information from the user depending on the type of variable assigned to the statement. The INPUT statement by itself will always force a question mark to the screen, as in these examples:

INPUT Test$ ' question mark then flashing cursor on screen

PRINT "Enter your name";
INPUT UserName$ '         question mark at end of previous text

However, by using INPUT's optional text print feature, you can get rid of the forced question mark:

INPUT "Enter your name ", UserName$
INPUT "Enter your age ", Age%

If INPUT expects a numeric value, such as in the second line above, QB64 helps out by only allowing the user to type in numbers. Furthermore, if the numeric value expected is an integer QB64 will also block the decimal point since integers are whole numbers. These features were added by QB64 and were not available in Microsoft's QuickBasic. INPUT will also truncate, or trim off, any leading or trailing spaces the user may have typed in.

INPUT also supports multiple variable input on one line like so:

INPUT "Enter your name, followed by a comma, then your age ", UserName$, Age%

This method of input can be a bit tricky for end users to fill out correctly and is rarely used. Also, because of this feature it is impossible to use some punctuation when entering text to be saved in a string variable, because INPUT may mistake it as a user trying to fill in multiple variables.

- INPUT$ -

INPUT$ is a rarely used statement for inputting information from the keyboard because it's really meant to input information from files. But, it does have one handy use that you may end up using; hidden password entry. Type the following example in and execute it.

DIM Login$
DIM Password$

PRINT
PRINT " Welcome to Cyberdyne Systems 2000"
PRINT " ---------------------------------"
PRINT
PRINT " CSOS Ver 2.03 Copyright(c)2013 CyberDyne Systems Inc. All Rights Reserved"
PRINT
LOCATE 8, 2
PRINT "Password  : "
LOCATE 7, 2
INPUT "Login Name: ", Login$
LOCATE 8, 14, 1
Password$ = INPUT$(8)
PRINT "********"
IF Password$ = "password" THEN
    PRINT
    PRINT " Access granted."
    PRINT
    PRINT " Would you like to play a game of thermonuclear warfare?"
ELSE
    PRINT
    COLOR 31
    PRINT " INTRUDER ALERT!"
    BEEP
    COLOR 7
    PRINT
    PRINT " Launching all nuclear warheads now toward human targets."
    _DELAY 3
    PRINT " Initiating T1000 battle droids."
    _DELAY 3
    PRINT " Booting hunter/killer BIOS routines."
    _DELAY 3
    PRINT
    PRINT " Have a nice day. "; CHR$(1)
END IF
DO: LOOP UNTIL INKEY$ = CHR$(27) ' exit program when ESC key pressed
SYSTEM

INPUT$ requires the number of keystrokes to wait for before continuing on. In the example you can see the statement INPUT$(8) was used to wait for 8 keystrokes. Once 8 keystrokes have been detected, and saved into Password$, the program continues on. Enter the correct password, password, and all is good. Enter the wrong password and the robots take over. Simple enough.

- LINE INPUT -

LINE INPUT is used to enter a literal string of information, regardless of type or punctutation. Because of this behavior the only type of variable that can be used with LINE INPUT is a string. If the user enters a numeric value using LINE INPUT the text will be seen as a literal string and not numeric information. Your program will need to convert the string to a true numeric value before treating it as such (there is a command to do this which will be discussed in a later task). LINE INPUT will never force a question mark on the screen either.

LINE INPUT Test$ ' a flashing cursor on the screen, no question mark
LINE INPUT "Enter your name ", UserName$

Once again remember that the only type of variable that can be used with LINE INPUT is a string. The literal string saved into the variable will retain all punctuation marks and any leading or trailing spaces that the user may have entered as well.


- INKEY$ -

Most games today need real-time user input from the keyboard to be effective. For example, if the user presses the "W" key the player moves forward, the "S" key and the player moves backwards, and so on. INPUT and its variations are no good for this kind of interaction, however the INKEY$ function can accomplish this for you. Let's once again start off with a simple example program that we'll investigate in detail.


'--------------------------------
'- Variable Declaration Section -
'--------------------------------

DIM KeyPress$ ' holds the key user is currently pressing
DIM x%, y% '    x,y coordinate of circle

'----------------------------
'- Main Program Begins Here -
'----------------------------

SCREEN 12 '                                  initiate graphics screen
x% = 319 '                                   set x,y circle coordinates
y% = 239
DO '                                         begin main loop
    CLS '                                    clear the screen
    LOCATE 2, 25 '                           position text cursor
    PRINT "Use WASD to move circle around" ' print user instructions
    LOCATE 29, 27 '                          position text cursor
    PRINT "Press ESC to leave program"; '    print user instructions
    CIRCLE (x%, y%), 50, 10 '                draw bright green circle
    PAINT (x%, y%), 2, 10 '                  fill in with dark green
    KeyPress$ = INKEY$ '                     grab any key being pressed
    IF KeyPress$ = "w" THEN y% = y% - 1 '    if w then decrement y
    IF KeyPress$ = "s" THEN y% = y% + 1 '    if s then increment y
    IF KeyPress$ = "a" THEN x% = x% - 1 '    if a then decrement x
    IF KeyPress$ = "d" THEN x% = x% + 1 '    if d then increment x
    IF y% < 49 THEN y% = 49 '                stop circle at top edge
    IF y% > 429 THEN y% = 429 '              stop circle at bottom edge
    IF x% < 49 THEN x% = 49 '                stop circle at left edge
    IF x% > 589 THEN x% = 589 '              stop circle at right edge
    _DISPLAY '                               update changes to the screen
LOOP UNTIL KeyPress$ = CHR$(27) '            exit program if ESC pressed
SYSTEM '                                     return to Windows

SlowPoke
Figure 1 - A green circle that moves at a snail's pace

After playing around with the program I'm sure you found a few limitations to INKEY$. Before continuing on though we need to discuss a little about how the keyboard works. The motherboard's BIOS (Basic Input Output System) is in charge of keeping track of key presses. When the BIOS detects a key press it places the key's ASCII (pronouned "ass-key") value in a keyboard buffer. This buffer can hold up to 16 key presses and it's up to the computer's operating system, in most cases Windows, to check this buffer now and then. If the operating system detects a value in the keyboard buffer it passes it on to the current running program. In some cases it is possible to type faster than the operating system checks the buffer and this is the reason the buffer can hold up to 16 keystroke values ahead.

The BIOS however puts a speed limit on just how fast key values can be placed into the buffer. If you were to enter your computer's BIOS, or CMOS setup program, you would find where you can change this speed limit. The speed limit ranges from 6 to 30 key press repeats per second on most computers. The BIOS also forces a slight "repeating key" delay, that is if you hold a key down a delay will happen before the key starts to repeat. This is to keep long key presses from being seen as the user wanting the key to repeat. The BIOS delay can range from 250ms (a quater second) up to a full second. When you run the example program above you see this in action. Holding one of the direction keys down immediatly moves the circle in that direction one pixel, but it's only after a slight pause that the circle starts to repeatedly move in that direction. The reason it moves so slowly is because of the speed limit imposed by the computer's BIOS routines. The operating system will typically let you go into its keyboard settings (for instance, control panel in Windows) to over-ride the BIOS settings, but if you were to remove the delay, or set the repeating key too high, you would find that every day operations done on the keyboard would get very tricky. In other words the delay and speed limit are there for good reason. Something else the BIOS can't do is place a value in the buffer that denotes two keys being pressed at the same time. This is why in the example program above you can't move in a diagonal direction. Holding down the "s" and "d" keys together will not result in the circle moving down and to the right. The last key you pressed will always take precedence.


When INKEY$ is used it asks the operating system for the next value in the keyboard buffer. This is why in the example program the variable KeyPress$ is used to hold the current INKEY$ value. If you were to repeatedly ask for INKEY$'s value it would constantly change because with each use the next buffer value is grabbed (or null if none). In other words, if we wanted to check if the user presses either "y" for yes, or "n" for no, this would not work:

DO
    IF INKEY$ = "y" THEN PRINT "YES"
    IF INKEY$ = "n" THEN PRINT "NO"
LOOP

The first call to INKEY$ would pull the next available value from the keyboard buffer. So if the user pressed "n" the first line would not do anything, as it should. However, the second line's INKEY$ once again pulls the next available value from the keyboard buffer (which in all likelyhood will be nothing) resulting in the second line never doing anything. Assigning INKEY$'s value to a variable however overcomes this problem:

DO
    KeyPress$ = INKEY$
    IF KeyPress$ = "y" THEN PRINT "YES"
    IF KeyPress$ = "n" THEN PRINT "NO"
LOOP


Now, since KeyPress$ holds the next available buffer value it can be checked by multiple statements before the program loops back and grabs the next available buffer value.

A rule of thumb when using INKEY$ is if a key being pressed results in a character printing on the screen the character itself can be used to test INKEY$. For example, the keys "w", "a", "s" and "d" all result in letters being printed to the screen, so this works:

    KeyPress$ = INKEY$
    IF KeyPress$ = "w" THEN y% = y% - 1 '    if w then decrement y
    IF KeyPress$ = "s" THEN y% = y% + 1 '    if s then increment y
    IF KeyPress$ = "a" THEN x% = x% - 1 '    if a then decrement x
    IF KeyPress$ = "d" THEN x% = x% + 1 '    if d then increment x

However, how would you test for the TAB, BACKSPACE, ENTER or even the ESCAPE key? These keys can be tested for with INKEY$ but you'll need to test for the ASCII value of the key instead of a character.

DO
    KeyPress$ = INKEY$
    IF KeyPress$ = CHR$(13) THEN PRINT "ENTER pressed"
    IF KeyPress$ = CHR$(9) THEN PRINT "TAB pressed"
    IF KeyPress$ = CHR$(8) THEN PRINT "BACKSPACE pressed"
    IF KeyPress$ = CHR$(27) THEN PRINT "ESCAPE pressed"
LOOP UNTIL KeyPress$ = CHR$(27) ' leave loop when ESC key pressed


ASCII stands for American Standard Code for Information Interchange and was developed in the 1950's as a way for different computers by different manufacturers to have a common set of characters that all computers would recognize. When information is sent from one computer to another through a network, such as phone lines or Ethernet, the data received is compared to a built in ASCII chart in the computer's ROM. This ensures that if the sending computer sends the letter "A", or ASCII code 65, the receiving computer sees this number 65 as an "A" as well. Almost every key on a keyboard can be represented by an ASCII code number so all programmers have an ASCII chart handy when needed. (some keys use a combination of two ASCII numbers which will be discussed later).

QB64's CHR$() function is used to grab a character from the computer's built-in ASCII chart and save it as a single string character. In order to test for the ESCAPE key you would need to use CHR$ to grab the character from the ASCII table first, then use it as a test:

DO
LOOP UNTIL INKEY$ = CHR$(27)

The above statements would continue to loop until the ESCAPE key is pressed because the ASCII value of 27 equates to the ESCAPE key.


INKEY$ is perfect for handling situations where real-time input is needed but its shortcomings are irrelevant. Situations like these call for individual keystrokes such as a menu like the next example provides.

'--------------------------------
'- Variable Declaration Section -
'--------------------------------

DIM KeyPress$ '  hold the value of a user key press
DIM Highlight% ' the current highlighted menu entry
DIM Selected% '  the selected menu entry
DIM Count% '     generic counter

'----------------------------
'- Main Program Begins Here -
'----------------------------

Highlight% = 1 '                              set initial highlighted entry
Selected% = 0 '                               no entry selected yet
LOCATE 2, 30 '                                position text cursor on screen
PRINT "Example Menu System" '                 print menu title
LOCATE 13, 22 '                               position text cursor on screen
PRINT "Use UP/DOWN arrow keys to highlight" ' print instructions to user
LOCATE 14, 24 '                               position text cursor
PRINT "Press ENTER to make a selection" '     print more instructions to user
DO '                                          begin main loop
    FOR Count% = 1 TO 5 '                     create 5 dummy menu entries
        LOCATE Count% + 5, 32 '               position text cursor on screen
        IF Highlight% = Count% THEN '         is this the highlghted entry?
            COLOR 30, 1 '                     yes, text flashing yellow on blue
        ELSE '                                no
            COLOR 14, 0 '                     text yellow on black
        END IF
        PRINT " Menu Option"; Count% '        display the menu entry
    NEXT Count%
    DO '                                      begin key press loop
        KeyPress$ = INKEY$ '                  get key from buffer
        _LIMIT 30 '                           30 loops per second (don't hog CPU)
    LOOP UNTIL KeyPress$ <> "" '              loop back if no key pressed (null)
    IF KeyPress$ = CHR$(13) THEN '            did user press ENTER?
        Selected% = Highlight% '              yes, remember which entry selected
    ELSEIF KeyPress$ = CHR$(0) + "H" THEN '   no, did user press UP ARROW?
        IF Highlight% <> 1 THEN '             yes, already on first entry?
            Highlight% = Highlight% - 1 '     no, move up one entry
        END IF
    ELSEIF KeyPress$ = CHR$(0) + "P" THEN '   no, did user press DOWN ARROW?
        IF Highlight% <> 5 THEN '             yes, already on last entry?
            Highlight% = Highlight% + 1 '     no, move down one entry
        END IF
    END IF
LOOP UNTIL Selected% <> 0 '                   loop back if nothing selected
LOCATE 18, 2 '                                position text cursor
COLOR 15, 0 '                                 text bright white on black
PRINT "You chose menu option"; Selected% '    report which entry chosen

Some keys are read by INKEY$ as two character combinations that start with CHR$(0). In the menu example program above you can see that the up and down arrow keys are two such keys that do this:

    ELSEIF KeyPress$ = CHR$(0) + "H" THEN '   no, did user press UP ARROW?

CHR$(0) + "H" is returned by the UP ARROW key.

    ELSEIF KeyPress$ = CHR$(0) + "P" THEN '   no, did user press DOWN ARROW?

CHR$(0) + "P" is returned by the DOWN ARROW key. A listing of these special two key combinations can be found here on the QB64 wiki page describing the INKEY$ function.


- _KEYDOWN -

Having read the history of BASIC in TASK 2 you'll remember that QB64 is not an official release of Microsoft's QuickBasic, but instead a language that clones QuickBasic and adds new commands. The commands that QB64 added all start with an underscore character ( _ ) and the reason for this is to ensure that programs written in QuickBasic since 1982 will still operate in QB64 to this day. For example, we are about to discuss the _KEYDOWN function that QB64 added. But, what if someone in 1990 made a function or subroutine called KEYDOWN? If QB64 would have called this new function by the same name, the program would get confused and not been able to run. In order to get around this problem the creator of QB64 decided to add the underscore character to the beginning of new commands ensuring (well 99.99% sure) that noone would have made a function or subroutine by the same exact name. From here on out when a new statement is introduced and it contains an underscore at the beginning, it's a new command added to QB64.

_KEYDOWN performs much the same way as INKEY$ but with a few very important differences. First, _KEYDOWN does not rely on the keyboard buffer for its input but rather scans the keyboard's hardware directly. This allows the programmer to detect multiple keys being pressed at the same time. Also, _KEYDOWN is not burdened by a repeat delay or speed limit on key repeats. Let's rewrite the green circle code to use _KEYDOWN this time and see if there is any difference in the way the code operates.

'--------------------------------
'- Variable Declaration Section -
'--------------------------------

DIM x%, y% '    x,y coordinate of circle

'----------------------------
'- Main Program Begins Here -
'----------------------------

SCREEN 12 '                                  initiate graphics screen
x% = 319 '                                   set x,y circle coordinates
y% = 239
DO '                                         begin main loop
    CLS '                                    clear the screen
    LOCATE 2, 25 '                           position text cursor
    PRINT "Use WASD to move circle around" ' print user instructions
    LOCATE 29, 27 '                          position text cursor
    PRINT "Press ESC to leave program"; '    print user instructions
    CIRCLE (x%, y%), 50, 10 '                draw bright green circle
    PAINT (x%, y%), 2, 10 '                  fill in with dark green
    IF _KEYDOWN(119) THEN y% = y% - 1 '      if w then decrement y
    IF _KEYDOWN(115) THEN y% = y% + 1 '      if s then increment y
    IF _KEYDOWN(97) THEN x% = x% - 1 '       if a then decrement x
    IF _KEYDOWN(100) THEN x% = x% + 1 '      if d then increment x
    IF y% < 49 THEN y% = 49 '                stop circle at top edge
    IF y% > 429 THEN y% = 429 '              stop circle at bottom edge
    IF x% < 49 THEN x% = 49 '                stop circle at left edge
    IF x% > 589 THEN x% = 589 '              stop circle at right edge
    _DISPLAY '                               update changes to the screen
LOOP UNTIL _KEYDOWN(27) '                    exit program if ESC pressed
SYSTEM '                                     return to Windows

Woohoo! Huge difference in speed and performance! _KEYDOWN always requires the ASCII value of the key to check for, such as the value 119 for the lowercase "w" key. But, just like with INKEY$, certain keys such as the arrow keys still return different values other than what a standard ASCII chart can deliver. For instance, _KEYDOWN(20480) is the DOWN ARROW key because of this key returning two values instead of one. A listing of these special key values are listed on the QB64 wiki page here describing the _KEYDOWN function.

- _KEYHIT -

_KEYHIT returns the ASCII value of a key being pressed and just like INKEY$ uses the keyboard buffer, repeat delay and speed limit imposed by BIOS. Here is our green circle example code using _KEYHIT.

'--------------------------------
'- Variable Declaration Section -
'--------------------------------

DIM KeyPress& ' the key user is pressing
DIM x%, y% '    x,y coordinate of circle

'----------------------------
'- Main Program Begins Here -
'----------------------------

SCREEN 12 '                                  initiate graphics screen
x% = 319 '                                   set x,y circle coordinates
y% = 239
DO '                                         begin main loop
    CLS '                                    clear the screen
    LOCATE 2, 25 '                           position text cursor
    PRINT "Use WASD to move circle around" ' print user instructions
    LOCATE 29, 27 '                          position text cursor
    PRINT "Press ESC to leave program"; '    print user instructions
    CIRCLE (x%, y%), 50, 10 '                draw bright green circle
    PAINT (x%, y%), 2, 10 '                  fill in with dark green
    KeyPress& = _KEYHIT '                    get key user is pressing
    IF KeyPress& = 119 THEN y% = y% - 1 '    if w then decrement y
    IF KeyPress& = 115 THEN y% = y% + 1 '    if s then increment y
    IF KeyPress& = 97 THEN x% = x% - 1 '     if a then decrement x
    IF KeyPress& = 100 THEN x% = x% + 1 '    if d then increment x
    IF y% < 49 THEN y% = 49 '                stop circle at top edge
    IF y% > 429 THEN y% = 429 '              stop circle at bottom edge
    IF x% < 49 THEN x% = 49 '                stop circle at left edge
    IF x% > 589 THEN x% = 589 '              stop circle at right edge
    _DISPLAY '                               update changes to the screen
LOOP UNTIL _KEYDOWN(27) '                    exit program if ESC pressed
SYSTEM '                                     return to Windows

After running this example you are probably thinking, "What is the point? INKEY$ acts exactly the same lame way.", right? Well, _KEYHIT has a hidden trick up its sleeve. _KEYHIT can tell you when a key has been released as well. When a key is released its ASCII value is returned as a negative number and stored in the keyboard buffer so you'll always know when a key has been pressed and then released. Here is our green circle example code again but this time checking for when keys have been released.

'--------------------------------
'- Variable Declaration Section -
'--------------------------------

DIM KeyPress& ' the value of the key user is pressing
DIM x%, y% '    x,y coordinate of circle
DIM GoUp% '     status of w key
DIM GoDown% '   status of s key
DIM GoLeft% '   status of a key
DIM GoRight% '  status of d key

'----------------------------
'- Main Program Begins Here -
'----------------------------

SCREEN 12 '                                  initiate graphics screen
x% = 319 '                                   set x,y circle coordinates
y% = 239
DO '                                         begin main loop
    CLS '                                    clear the screen
    LOCATE 2, 25 '                           position text cursor
    PRINT "Use WASD to move circle around" ' print user instructions
    LOCATE 29, 27 '                          position text cursor
    PRINT "Press ESC to leave program"; '    print user instructions
    CIRCLE (x%, y%), 50, 10 '                draw bright green circle
    PAINT (x%, y%), 2, 10 '                  fill in with dark green
    KeyPress& = _KEYHIT '                    get key the user is pressing
    IF KeyPress& = 119 THEN GoUp% = 1 '      remember when w key pressed
    IF KeyPress& = -119 THEN GoUp% = 0 '     remember when w key released
    IF KeyPress& = 115 THEN GoDown% = 1 '    remember when s key pressed
    IF KeyPress& = -115 THEN GoDown% = 0 '   remember when s key released
    IF KeyPress& = 97 THEN GoLeft% = 1 '     remember then a key pressed
    IF KeyPress& = -97 THEN GoLeft% = 0 '    remember when a key released
    IF KeyPress& = 100 THEN GoRight% = 1 '   remember when d key pressed
    IF KeyPress& = -100 THEN GoRight% = 0 '  remember when d key released
    IF GoUp% = 1 THEN y% = y% - 1 '          if w key then decrement y
    IF GoDown% = 1 THEN y% = y% + 1 '        if s key then increment y
    IF GoLeft% = 1 THEN x% = x% - 1 '        if a key then decrement x
    IF GoRight% = 1 THEN x% = x% + 1 '       if d key then increment x
    IF y% < 49 THEN y% = 49 '                stop circle at top edge
    IF y% > 429 THEN y% = 429 '              stop circle at bottom edge
    IF x% < 49 THEN x% = 49 '                stop circle at left edge
    IF x% > 589 THEN x% = 589 '              stop circle at right edge
    _DISPLAY '                               update changes to the screen
LOOP UNTIL _KEYDOWN(27) '                    exit program if ESC pressed
SYSTEM '                                     return to Windows

Woohoo! We have our speed and performance back again. You may have noticed we added four new variables to this example, GoUp%, GoDown%, GoLeft% and GoRight%. These variables are being used as latches. When any of our four direction keys have been pressed the corresponding latch variable gets set to 1. When any of the four direction keys has been released the corresponding latch variable gets set to 0. As long as a latch variable is set to 1, meaning the key has been pressed but not yet released, the circle moves in that direction. Pressing a second key turns another latch on and we can achieve diagonal movement. Granted, _KEYDOWN is a much easier way to handle this example code as it requires less programming but there will be times when you need to know when a key has been pressed and again when it has been released, and _KEYHIT is the perfect function for this task. Also, just like with INKEY$ and _KEYDOWN some keys return two character codes and these codes can be found on the QB64 wiki here describing the _KEYHIT function.

-- Mouse Input --

- _MOUSEX and _MOUSEY -

QB64 adds full support for mouse input through some very ingenius commands. The _MOUSEX function returns the current x coordinate of the mouse pointer and the _MOUSEY function returns the current y coordinate of the mouse pointer. Here again is the green circle example code modified to use the mouse instead of the keyboard to move the circle around.

'--------------------------------
'- Variable Declaration Section -
'--------------------------------

DIM KeyPress$ ' holds the key user is currently pressing
DIM x%, y% '    x,y coordinate of circle

'----------------------------
'- Main Program Begins Here -
'----------------------------

SCREEN 12 '                                   initiate graphics screen
x% = 319 '                                    set x,y circle coordinates
y% = 239
_MOUSEHIDE '                                  Make mouse pointer invisible
DO '                                          begin main loop
    CLS '                                     clear the screen
    LOCATE 2, 25 '                            position text cursor
    PRINT "Use MOUSE to move circle around" ' print user instructions
    LOCATE 29, 27 '                           position text cursor
    PRINT "Press ESC to leave program"; '     print user instructions
    CIRCLE (x%, y%), 50, 10 '                 draw bright green circle
    PAINT (x%, y%), 2, 10 '                   fill in with dark green
    DO WHILE _MOUSEINPUT '                    get latest mouse information
    LOOP
    x% = _MOUSEX '                            retrieve mouse X position
    y% = _MOUSEY '                            retrieve mouse Y potition
    IF y% < 49 THEN y% = 49 '                 keep circle at top edge
    IF y% > 429 THEN y% = 429 '               keep circle at bottom edge
    IF x% < 49 THEN x% = 49 '                 keep circle at left edge
    IF x% > 589 THEN x% = 589 '               keep circle at right edge
    _DISPLAY '                                update changes to the screen
LOOP UNTIL _KEYDOWN(27) '                     exit program if ESC pressed
SYSTEM '                                      return to Windows

QB64's creator designed the mouse routines in such a way that a mouse buffer is used in much the same way the keyboard's buffer works. All mouse movements and actions are placed into the mouse buffer for retrieval as they happened. This piece of code here:

DO WHILE _MOUSEINPUT '                        get latest mouse information
LOOP


empties the mouse buffer to the latest readings to ensure the latest mouse position is retrieved. The _MOUSEINPUT function will return a value of -1 if there is any information in the mouse buffer and 0 (zero) when all updates from the mouse have been read. In fact, _MOUSEINPUT must be called at least once before any of the mouse commands that retrieve information from the mouse can be used. Placing _MOUSEINPUT in a loop simply ensures that the latest mouse information is ready for retrieval. There will be times when you need to know every single mouse movement and click that happened between events so a single _MOUSEINPUT statement outside of a loop will then be used. You may have witnessed this behavior in Windows when you click on something, and while that event is taking place you click on something else. After the first event finishes the second mouse click is then acted upon. In other words, Windows uses a mouse buffering system as well to make sure that all mouse events are seen and used.

Note: -1 in most computer languages relates to TRUE and 0 (zero) relates to FALSE. So, as long as there are mouse updates in the mouse buffer _MOUSEINPUT will have the value of -1. Replacing the function _MOUSEINPUT with the words TRUE and FALSE shows how this works. DO WHILE TRUE will continue to loop as long as the line of code is TRUE (or -1). But when the line of code becomes DO WHILE FALSE (or 0) the loop ends. In fact, most computer languages will equate any non-zero value with TRUE. There will be more examples of this type of action in later examples that will be pointed out.

Another mouse related statement you see in the previous example is _MOUSEHIDE that simply hides the default mouse cursor (the arrow). Plase a REM statement ( ' ) in front of this line and run the code again to see the mouse pointer moving along with the arrow. If after hiding the mouse pointer you wish to show it again simply use the _MOUSESHOW statement to make it visible again.

Yet another handy mouse related statement is _MOUSEMOVE which will force the mouse pointer to a specified location on the screen. This can be used for instances such as moving the mouse pointer to default buttons that you may have created on the screen.

- _MOUSEBUTTON -

_MOUSEBUTTON is used to retrieve the status of all mouse buttons. Here is our circle example again but this time clicking the left or right mouse buttons changes the circle's color.

'--------------------------------
'- Variable Declaration Section -
'--------------------------------

DIM KeyPress$ '    holds the key user is currently pressing
DIM x%, y% '       x,y coordinate of circle
DIM LeftClick% '   -1 when left button clicked, 0 when not
DIM RightClick% '  -1 when right button clicked, 0 when not
DIM CircleColor% ' color of circle
DIM PaintColor% '  inside paint color of circle

'----------------------------
'- Main Program Begins Here -
'----------------------------

SCREEN 12 '                                        initiate graphics screen
x% = 319 '                                         set x,y circle coordinates
y% = 239
CircleColor% = 10 '                                set initial circle color
PaintColor% = 2 '                                  set initial paint color
_MOUSEHIDE '                                       Make mouse pointer invisible
DO '                                               begin main loop
    CLS '                                          clear the screen
    LOCATE 2, 25 '                                 position text cursor
    PRINT "Use MOUSE to move circle around" '      print user instructions
    LOCATE 3, 22 '                                 position text cursor
    PRINT "Mouse buttons to change circle color" ' print user instructions
    LOCATE 29, 27 '                                position text cursor
    PRINT "Press ESC to leave program"; '          print user instructions
    CIRCLE (x%, y%), 50, CircleColor% '            draw circle
    PAINT (x%, y%), PaintColor%, CircleColor% '    fill circle
    DO WHILE _MOUSEINPUT '                         get latest mouse information
    LOOP
    x% = _MOUSEX '                                 retrieve mouse X position
    y% = _MOUSEY '                                 retrieve mouse Y potition
    LeftClick% = _MOUSEBUTTON(1) '                 retrieve left button status
    RightClick% = _MOUSEBUTTON(2) '                retrieve right button status
    IF LeftClick% THEN '                           was left button clicked?
        CircleColor% = 10 '                        yes, set circle color (bright green)
        PaintColor% = 2 '                          set circle fill color (green)
    ELSEIF RightClick% THEN '                      no, was right button clicked?
        CircleColor% = 12 '                        yes, set circle color (bright red)
        PaintColor% = 4 '                          set circle fill color (red)
    END IF
    IF y% < 49 THEN y% = 49 '                      keep circle at top edge
    IF y% > 429 THEN y% = 429 '                    keep circle at bottom edge
    IF x% < 49 THEN x% = 49 '                      keep circle at left edge
    IF x% > 589 THEN x% = 589 '                    keep circle at right edge
    _DISPLAY '                                     update changes to the screen
LOOP UNTIL _KEYDOWN(27) '                          exit program if ESC pressed
SYSTEM '                                           return to Windows

Mouse buttons are assigned numbers to be used with the _MOUSEBUTTON function. _MOUSEBUTTON(1) refers to the left mouse button, _MOUSEBUTTON(2) refers to the right mouse button, _MOUSEBUTTON(3) refers to the mouse center button (usually when the wheel is clicked) and numbers higher than this refer to any more buttons your particular mouse may have. With some mice you can tilt the mouse wheel left and right and these buttons would be 4 and 5 respectively. If a mouse button is clicked _MOUSEBUTTON will return a value of -1 (or TRUE) and if the mouse button is not clicked _MOUSEBUTTON will return a value of 0 (zero, or FALSE).

It's because of this TRUE/FALSE nature of -1/0 that the following IF...THEN statement works:

IF LeftClick% THEN

Substituting the word TRUE for the value of -1 if the left mouse button is pressed makes the statement read:

IF TRUE THEN

which activates the IF...THEN statement.


There are other mouse related commands available however the ones discussed in this task will do for now. If you wish to investigate the additional mouse commands go here in the QB64 wiki.

-- Your Turn --












-- COMMAND REFERENCE --


Commands learned in previous tasks:

PRINT
INPUT
DIM
REM or '
CONST
TIME$
VAL()
IF...THEN
AND
END
GOTO
SELECT CASE...END SELECT
CASE
TO
IS
CASE ELSE
colon ( : )
FOR...NEXT
STEP
CLS
SLEEP
_DELAY
SYSTEM
DO...LOOP UNTIL (and variations)
WHILE...WEND
SCREEN
LINE
CIRCLE
PAINT
PSET
SUB
END SUB
FUNCTION
END FUNCTION
SHARED
SQR()
^ (exponent)

New commands introduced in this task:

INPUT$
LINE INPUT
INKEY$
CHR$()
_KEYDOWN
_KEYHIT
_MOUSEX
_MOUSEY
_MOUSEINPUT
_MOUSEHIDE
_MOUSESHOW
_MOUSEMOVE
_MOUSEBUTTON

Concepts learned in previous tasks:

execute
statement
expression
literal string
syntax error
variables
type declarations
literal strings
concatenation
integers
long integers
single precision
double precision
strings
null strings
reserved words
operators ( +, -, *, / )
declare
constant
relational operators
functions
nesting
order of operations
Boolean
conditions
indenting
loops
labels
frame rate
controlled loops
conditional loops
Monochrome
Grayscale
pixels
bits per pixel
radian
counter-clockwise
aspect ratio
algorithm
subroutine
function
local variables
RAM
global variables
Pythagorean Theorem

New concepts introduced in this task:

BIOS
ASCII
buffer
CMOS
ROM
ASCII chart