QB64 Phoenix Edition
Controller Library - Printable Version

+- QB64 Phoenix Edition (https://qb64phoenix.com/forum)
+-- Forum: QB64 Rising (https://qb64phoenix.com/forum/forumdisplay.php?fid=1)
+--- Forum: Expanding Horizons (Libraries) (https://qb64phoenix.com/forum/forumdisplay.php?fid=21)
+---- Forum: Terry Ritchie (https://qb64phoenix.com/forum/forumdisplay.php?fid=31)
+---- Thread: Controller Library (/showthread.php?tid=1615)

Pages: 1 2


Controller Library - TerryRitchie - 04-12-2023

------------------------------------------------------

05/17/23 - UPDATE

Version 1.10 is now available (code below updated as well as attached ZIP file)


NOTE: This library will now require QB64PE version 3.7.0 and above.

This version now adds the following capabilities:

- Save and load user defined buttons ( __LOAD_BUTTONS, __SAVE_BUTTONS )
- The ability to detect new controllers plugged and existing controllers unplugged/plugged back in ( __NEW_CONTROLLER, __CONNECTED )
- Remove all controller associated user defined buttons ( __REMOVE_CONTROLLER )
- Prior versions automatically created buttons based on integer variables. A change was needed to facilitate the loading of saved button configurations.
  __MAKE_BUTTON is now required to initialize user defined buttons (see documentation in CONTROLLER.BI for more info).
------------------------------------------------------



I wrote a controller library for use with the keyboard, mouse, and joysticks / game pads.



The first code is CONTROLLER.BI to be placed at the top of your code. It also contains the library documentation.



The second code is CONTROLLER.BM to be placed at the bottom of your code.



The next three pieces of code are examples showing the use of the library. A ZIP file is also attached that contains all the code as well. A fourth demo named "Configure_Buttons.BAS" is also included that is a mini-game that highlights all the features of the library.



Give it a whirl and let me know if you find any bugs or changes that I should make to the library. I have not incorporated WHEEL routines yet. Having a bit of an issue getting WHEEL functions to work properly.



Code: (Select All)
'----------------------------
' Controller Library V1.10
' Terry Ritchie
' May 17th, 2023
' Written using QB64PE v3.7.0
'----------------------------
' CONTROLLER.BI
'----------------------------
'
' TODO: Add WHEEL routines
'       Create PDF instructions
'
'----------------------------
' 04/10/23 V1.0  - Initial Release
' 04/26/23 V1.01 - Corrected slot reassigning issues
' 05/17/23 V1.10 - Added __MAKE_BUTTON, __CONNECTED, __SAVE_BUTTONS, __LOAD_BUTTONS, __NEW_CONTROLLER, __REMOVE_CONTROLLER
'                - The library now has the ability to save and load controller user defined button configurations
'                - You can now detect when a controller has been plugged in or unplugged
'                - A controller's user defined button assignments can be removed when the controller has been unplugged
'----------------------------



' +---------------------------------------+
' |                                       |
' |     HOW QB64 HANDLES CONTROLLERS      |
' |                                       |
' +---------------------------------------+
'
' QB64's controller commands work by identifying the connected controllers at program start up using _DEVICES. This list of identified controllers can
' only be added to during program execution. If a controller is unplugged the controller's id number is not removed from the list. Instead, the
' controller will be listed as [DISCONNECTED] through _DEVICE$. When the controller is plugged back in the [DISCONNECTED] will be removed from the
' controller's identifying string returned by _DEVICE$.
'
' This library will support up to six controllers (see the list below in "INITIALIZING THE LIBRARY") connected at one time. The behavior above will only
' become an issue if your user, for whatever reason, has plugged in, and then unplugged, so many controllers that the device list contains all
' [DISCONNECTED] controllers and a new controller plugged in occupies id #7. That controller will not be detected, only the previous controllers the
' user plugged in. This scenario is highly unlikely but, users being users, it may happen. You can test for this condition by checking if _DEVICES is
' ever greater than 6.



' +---------------------------------------+
' |                                       |
' |       INITIALIZING THE LIBRARY        |
' |                                       |
' +---------------------------------------+
'
' The first subroutine that MUST be called is __INITIALIZE_CONTROLLERS. This subroutine initializes all the associated controller variables and
' identifies all connected controllers. Up to 6 total controllers can be detected and used:
'
' - 1 keyboard,  1 mouse,  and 4 joysticks/game pads
' - 1 leyboard,  no mouse, and 5 joysticks/game pads
' - no keyboard, 1 mouse,  and 5 joysticks/game pads
' - no keyboard, no mouse, and 6 joysticks/game pads
'
' Once the connected controllers have been identified the following shared integers will be set with controller ID numbers:
'
' __KEYBOARDCID      Always a value of 1 when a keyboard is detected
' __MOUSECID         Always a value of 2 if a keyboard is also present, 1 if no keyboard detected
' __JOYPAD1CID       The first joystick/game pad detected. Always a value of 3 if a keyboard and mouse present, 1 or 2 otherwise
' __JOYPAD2CID       The second joystick/game pad detected. Always a value of 4 if a keyboard and mouse present, 2 or 3 otherwise
' __JOYPAD3CID       The third joystick/game pad detected. Always a value of 5 if a keyboard and mouse present, 3 or 4 otherwise
' __JOYPAD4CID       The fourth joystick/game pad detected. Always a value of 6 if a keyboard and mouse present, 4 or 5 otherwise
' __JOYPAD5CID       The fifth joystick/game pad detected. ALWAYS A VALUE OF 5. Will only be detected if a keyboard or mouse is NOT present
' __JOYPAD6CID       The sixth joystick/game pad detected. ALWAYS A VALUE OF 6. Will only be detected if a keyboard and mouse are NOT present
'
' NOTE: If a certain controller is not found, for instance a mouse, then the corresponding variable will contain 0 (__MOUSECID = 0)
'
' These variables are now to be used to indentify the controller you wish to interact with. If a variable contains a value other than 0 the controller
' exists and will continue to exist until program termination.



' +---------------------------------------+
' |                                       |
' |        REDETECING CONTROLLERS         |
' |                                       |
' +---------------------------------------+
'
' If at any time you wish to rescan for currently connected controllers you can use this subroutine:
'
' __IDENTIFY_CONTROLLERS
'
' However, you'll more than likely never need to use this subroutine. The library is designed to automatically use __IDENTIFY_CONTROLLERS when a
' change in controller status occurs, such as a new controller plugged in, an existing controller unplugged, or an existing controller plugged back in.
' __INITIALIZE_CONTROLLERS automatically calls this subroutine as well.



' +---------------------------------------+
' |                                       |
' |       GET EXISTING CONTROLLERS        |
' |                                       |
' +---------------------------------------+
'
' The following functions can be used to determine the number and type of controllers found:
'
' __KEYBOARD_EXISTS         - determine if a keyboard controller was found, -1 (TRUE) or 0 (FALSE)
' __MOUSE_EXISTS            - determine if a mouse controller was found, -1 (TRUE) or 0 (FALSE)
' __JOYPAD_EXISTS(Number)   - determine if a joystick/game pad was found
'                             Number = the desired joystick/game pad to query (1 to 6)
'                             If the numbered game pad was found __JOYPAD_EXISTS returns the TOTAL NUMBER of joysticks/game pads found.
'
' Example:
'
' IF __KEYBOARD_EXISTS THEN PRINT "Found!" '                          was keyboard controller found?
' IF __MOUSE_EXISTS THEN PRINT "Found!" '                             was mouse controller found?
' NumberOfJoypads = __JOYPAD_EXISTS(__JOYPAD1CID) '                   query to determine if at least one joystick/game pad found
' IF NumberOfJoypads THEN '                                           at least 1 joystick found?
'     PRINT "Joystick 1 found!" '                                     yes, display result
'     PRINT "Total number of joysticks/game pads:"; NumberOfJoypads
' END IF



' +---------------------------------------+
' |                                       |
' |       GET CONTROLLER PROPERTIES       |
' |                                       |
' +---------------------------------------+
'
' The following functions can be used to query each controller for information:
'
' __CONTROLLER_NAME$(cid) - the descriptive name of the controller
' __BUTTON_TOTAL(cid)     - the number of buttons a controller has
' __AXIS_TOTAL(cid)       - the number of axes a controller has
'
' Example:
'
' IF __JOYPAD1CID THEN                                               ' or "IF __JOYPAD_EXISTS(1) THEN" would work as well
'     PRINT "Joystick 1 name  : "; __CONTROLLER_NAME$(__JOYPAD1CID)
'     PRINT "Buttons available:"; __BUTTON_TOTAL(__JOYPAD1CID)
'     PRINT "Axes available   :"; __AXIS_TOTAL(__JOYPAD1CID)
' END IF



' +---------------------------------------+
' |                                       |
' |       QUERY CONTROLLER DIRECTLY       |
' |                                       |
' +---------------------------------------+
'
' The following function can be used to determine if a controller is plugged in or unplugged:
'
' __CONNECTED(cid)
'
' The function will return a value of -1 (TRUE) if the controller is plugged in and 0 (FALSE) if the controller in unplugged.
'
' Example:
'
' IF __JOYPAD2CID THEN '                                      was joystick 2 detected at program startup?
'     IF __CONNECTED(__JOYPAD2CID) THEN '                     yes, is joystick 2 currently connected?
'         PRINT "Joystick 2 found and currently connected." ' yes, inform user
'     ELSE '                                                  no, joystick 2 has been unplugged
'         PRINT "Joystick 2 is currently unplugged." '        inform user
'     END IF
' END IF
'
' The following functions can be used to query buttons and axes directly from a controller:
'
' __CONTROLLER_BUTTON(cid, Button)
' __CONTROLLER_AXIS(cid, Axis)
'
' Example:
'
' IF __CONTROLLER_BUTTON(__JOYPAD1CID, 1) THEN '        is joystick button one down?
'     PRINT "Joystick button #1 pressed!" '             yes, report findings
' END IF
' IF __CONROLLER_BUTTON(__KEYBOARDCID, CLKEY_UP) THEN ' is keyboard UP ARROW key down?
'     PRINT "Keyboard UP ARROW key pressed!" '          yes, report findings
' END IF
' VerticalAxis = __CONTROLLER_AXIS(__JOYPAD1CID, 1) '   get current vertical axis of joystick/game pad 1



' +---------------------------------------+
' |                                       |
' |     RETRIEVING CONTROLLER EVENTS      |
' |                                       |
' +---------------------------------------+
'
' The __NEW_CONTROLLER function is used to identify when a new controller has been plugged in, and existing controller has been unplugged, or an
' existing controller has been plugged back in:
'
' Controller = __NEW_CONTROLLER(Event)
'
' Event will contain the controller event that was detected (if any):
'
'    0 - no events occurred
'    1 - an existing controller was unplugged       (the constant __UNPLUGGED     can be used to check for this event)
'    2 - an existing controller was plugged back in (the constant __PLUGGEDIN     can be used to check for this event)
'    3 - a new controller has been plugged in       (the constant __NEWCONTROLLER can be used to check for this event)
'
' The function will return the following values:
'
'      - a value of 0 (FALSE) if nothing has changed
'      - the value will contain the new controller id when a new controller is plugged in               (Event = 3     )
'      - the value will contain the controller id of a controller that was unplugged or plugged back in (Event = 1 or 2)
'
' Example:
'
' Controller = __NEW_CONTROLLER(Event) '                                                    check for a controller event
' IF Controller THEN '                                                                      has a controller event ocurred?
'     SELECT CASE Event '                                                                   yes, what happened?
'         CASE __NEW_CONTROLLER '                                                           a new controller was plugged in
'             PRINT "A new controller with the id of"; Controller; "has been plugged in."
'         CASE __PLUGGEDIN '                                                                an existing controller was plugged back in
'             PRINT "Controller id"; Controller; "has been plugged back in."
'         CASE __UNPLUGGED '                                                                an existing controller was unplugged
'             PRINT "Controller id"; Controller; "has been unplugged."
'     END SELECT
' END IF
'
' During game play there is no need to constantly check for controller events. Inside the main game loop during game play a check once per second
' will be more than enough. See the example program named "Configure_buttons.BAS" for a demonstration of this in action.



' +---------------------------------------+
' |                                       |
' |     CREATING USER DEFINED BUTTONS     |
' |                                       |
' +---------------------------------------+
'
' A user defined button can have up to 4 buttons or axes from various controllers associated with it. First, create an integer handle for each user
' defined button:
'
' DIM UP_Button AS INTEGER '    user defined button handles with up to four associated controller buttons and/or axes
' DIM DOWN_Button AS INTEGER
' DIM LEFT_Button AS INTEGER
' DIM RIGHT_Button AS INTEGER
'
' Next, the variables must be identified as user defined buttons using the __MAKE_BUTTON subroutine:
'
' __MAKE_BUTTON UP_Button '     __MAKE_BUTTON statement added with version 1.10
' __MAKE_BUTTON DOWN_Button '   __MAKE_BUTTON must be used with versions 1.10 and above
' __MAKE_BUTTOn LEFT_Button
' __MAKE_BUTTON RIGHT_Button
'
' The following subroutines allow for assigning a controller button and/or axes directly to a user defined button:
'
' __ASSIGN_BUTTON(Handle, cid, Button)
' __ASSIGN_AXIS(Handle, cid, Axis)
'
' Example:
'
' __ASSIGN_BUTTON UP_Button, __KEYBOARDCID, CLKEY_UP '       keyboard UP ARROW key assigned to UP_Button                     [SLOT1]
' __ASSIGN_BUTTON UP_Button, __KEYBOARDCID, CLKEY_W '        Keyboard W key also assigned to UP_Button                       [SLOT2]
' __ASSIGN_AXIS UP_Button, __JOYPAD1CID, -2 '                joystick vertical axis UP (-) also assigned to UP_Button        [SLOT3]
' __ASSIGN_BUTTON DOWN_Button, __KEYBOARDCID, CLKEY_DOWN '   keyboard DOWN ARROW key assigned to DOWN_Button                 [SLOT1]
' __ASSIGN_BUTTON DOWN_Button, __KEYBOARDCID, CLKEY_S '      keyboard S key also assigned to DOWN_Button                     [SLOT2]
' __ASSIGN_AXIS DOWN_Button, __JOYPAD1CID, 2 '               joystick vertical axis DOWN (+) also assigned to DOWN_Button    [SLOT3]
' __ASSIGN_BUTTON LEFT_Button, __KEYBOARDCID, CLKEY_LEFT '   keyboard LEFT ARROW key assigned to LEFT_Button                 [SLOT1]
' __ASSIGN_BUTTON LEFT_Button, __KEYBOARDCID, CLKEY_A '      keyboard A key also assigned to LEFT_Button                     [SLOT2]
' __ASSIGN_AXIS LEFT_Button, __JOYPAD1CID, -1 '              joystick horizontal axis LEFT (-) also assigned to LEFT_Button  [SLOT3]
' __ASSIGN_BUTTON RIGHT_Button, __KEYBOARDCID, CLKEY_RIGHT ' keyboard RIGHT ARROW key assigned to RIGHT_Button               [SLOT1]
' __ASSIGN_BUTTON RIGHT_Button, __KEYBOARDCID, CLKEY_D '     keyboard D key also assigned to RIGHT_Button                    [SLOT2]
' __ASSIGN_AXIS RIGHT_Button, __JOYPAD1CID, 1 '              joystick horizontal axis RIGHT (+) also asigned to RIGHT_Button [SLOT3]
'
' The above example now gives the player the option of using the keyboard ARROW keys, WASD keys, or the joystick to move in all four directions.
' Each of the above user defined buttons still have one slot remaining [SLOT4] and it could be populated with another joystick axis or perhaps
' the keyboard NUMBER PAD arrow keys if you wish.
'
' Joystick and game pad axis directions are defined with positive and negative values. A negative axis value either means UP or LEFT and a positive
' axis value either means DOWN or RIGHT depending on the axis being assigned. Axis deflections are detected when the axis is 50% or greater in
' deflection in a given direction. Top hats and D-Pads typically return a vale of -1 (-100%) or 1 (100%) while analog joystick inputs will change
' from -1 to 1 with a range of values in between. Analog joystick axes will register as a button press when they are deflected -.5 (-50%) to
' .5 (+50%) in either direction. (This .5 value can be changed using __SET_AXIS_THRESHOLD to suit your needs)




' +---------------------------------------+
' |                                       |
' |  SETTING AXIS THRESHOLD SENSITIVITY   |
' |                                       |
' +---------------------------------------+
'
' When using an axis as a button a certain axis deflection must be reached before the axis is considered "pressed". By default a joystick or game pad
' axis must be deflected at least 50% to reach this threshold. The following function can be used to change the threshold sensitivity amount:
'
' __SET_AXIS_THRESHOLD(Value)
'
' Example:
'
' __SET_AXIS_THRESHOLD .25 ' set axis button sensitivity to 25% deflection.
'
' Value can be any number from .01 (1%) to .99 (99%).



' +---------------------------------------+
' |                                       |
' | DETECTING A USER DEFINED BUTTON PRESS |
' |                                       |
' +---------------------------------------+
'
' The following function can be used to test if a user defined button is being pressed:
'
' __BUTTON_DOWN(Handle)
'
' Example:
'
' IF __BUTTON_DOWN(UP_Button) THEN
'     PRINT "Either the keyboard UP ARROW or W key was pressed or joystick 1 was pushed or pressed UP."
' END IF



' +---------------------------------------+
' |                                       |
' |  AUTO-ASSIGNING USER DEFINED BUTTONS  |
' |                                       |
' +---------------------------------------+
'
' Because of the wide variety of joytick and game pad controllers a user may connect it may be best to have the user define the keys, buttons, and
' axes they wish to use. The following subroutine can be used to have the user auto-assign user defined button assignments:
'
' __AUTOASSIGN_BUTTON(Handle)
'
' Example:
'
' __AUTOASSIGN_BUTTON UP_Button ' [SLOT1] wait for a button press or axis deflection
' __AUTOASSIGN_BUTTON UP_Button ' [SLOT2] wait for a button press or axis deflection
'
' __AUTOASSIGN_BUTTON will wait for a controller button press or axis deflection and then store that information into an available slot. Again,
' this can be done up to four times to fill the four available slots.
'
' NOTE: The mouse controller axes are ignored while __AUTOASSIGN_BUTTON waits for a controller button or axis. If you wish to assign mouse movements as
'       axes to a user defined button you'll need to do it manually with __ASSIGN_AXIS.



' +---------------------------------------+
' |                                       |
' |     REMOVING BUTTON ASSIGNMENTS       |
' |                                       |
' +---------------------------------------+
'
' The following subroutine can be used to clear button/axis assignments (slots) from a user defined button:
'
' __REMOVE_BUTTON(Handle, Slot)
'
' Example:
'
' __REMOVE_BUTTON UP_Button, 3   ' remove the assigned button/axis from slot 3
' __REMOVE_BUTTON DOWN_Button, 0 ' remove the assigned buttons/axes from all slots
'
' Slot can range from 1 to 4, or 0 if you wish to remove all user assigned buttons/axes.



' +---------------------------------------+
' |                                       |
' |    REMOVING A CONTROLLER'S BUTTONS    |
' |                                       |
' +---------------------------------------+
'
' The subroutine __REMOVE_CONTROLLER will remove all user assigned buttons associated with a controller. This is most useful when a controller has
' been detected as unplugged by __NEW_CONTROLLER and the associated assigned buttons need to be removed as well.
'
' __REMOVE_CONTROLLER cid
'
' Example:
'
' Controller = __NEW_CONTROLLER(Event) '                                                    check for a controller event
' IF Controller THEN '                                                                      has a controller event ocurred?
'     SELECT CASE Event '                                                                   yes, what happened?
'         CASE __NEW_CONTROLLER '                                                           a new controller was plugged in
'             PRINT "A new controller with the id of"; Controller; "has been plugged in."
'         CASE __PLUGGEDIN '                                                                an existing controller was plugged back in
'             PRINT "Controller id"; Controller; "has been plugged back in."
'             __LOAD_BUTTONS '                                                              load user defined buttons associated with controller (if they exist)
'         CASE __UNPLUGGED '                                                                an existing controller was unplugged
'             PRINT "Controller id"; Controller; "has been unplugged."
'             __REMOVE_CONTROLLER Controller '                                              remove controller's associated user defined buttons
'     END SELECT
' END IF



' +---------------------------------------+
' |                                       |
' | ENABLING/DISABLING BUTTON REASSIGNMENT|
' |                                       |
' +---------------------------------------+
'
' If an attempt is made to assign an axis or button that was previously assigned to a user defined button, the original assignment will be erased and
' replaced by the new assignment by default. This behavior can be changed using the following subroutines:
'
' __BUTTON_REASSIGN_ALLOWED
' __BUTTON_REASSIGN_NOT_ALLOWED
'
' Example:
'
' __BUTTON_REASSIGN_ALLOWED     ' allow previously assigned buttons/axes to be moved into a different user assigned button *DEFAULT*
' __BUTTON_REASSIGN_NOT_ALLOWED ' ignore requests to reassign buttons



' +---------------------------------------+
' |                                       |
' |       GETTING A BUTTON'S NAME         |
' |                                       |
' +---------------------------------------+
'
' When a user defined button is created a descriptive name is also generated and stored. You can use the following function to get a user defined button's
' name:
'
' __BUTTON_NAME$(Handle, Slot)
'
' Example:
'
' ButtonName$ = __BUTTON_NAME$(UP_Button, 1)
'
' Slot values range from 1 to 4.



' +---------------------------------------+
' |                                       |
' |  LOADING/SAVING USER DEFINED BUTTONS  |
' |                                       |
' +---------------------------------------+
'
' It's now possible to save and load user defined buttons associated to controller inputs. Use the __SAVE_BUTTONS subroutine to save the current
' set of user defined buttons and __LOAD_BUTTONS to load any user defined buttons that may be associated with a controller.
'
' __SAVE_BUTTONS ' save all currently configured user define buttons to configuration files
' __LOAD_BUTTONS ' load buttons that are associated with the currently connected controllers
'
' When __SAVED_BUTTONS is used a configration file for each attached controller is created. The name of the controller and the controller's id number
' ised used to create the file. For instance, if a system currently has a keyboard, mouse, Joystick, and game pad. The name of the joystick is
' "Saitek ST290 Pro" and the name of the game pad is "USB Game Pad". The joystick is using id#3 and the game pad is using id#4. The four configration
' files that will be created are:
'
'   - "Keyboard.ID1"
'   - "Mouse.ID2"
'   - "Saitek ST290 Pro.ID3"
'   - "USB Game Pad.ID4"
'
' All user assigned buttons will be saved in their assigned controller configuration file. It's possible to have multiple configration files for any
' given controller based on the id number it is using. For example, let's say the next time the program is started only the USB Game Pad is connected.
' It will be identified as using id number 3. Later on the user plugs in the Saitek ST290 Pro which will now be have an id of 4. When __SAVE_BUTTONS
' is used the four configration files will be as follows:
'
'   - "Keyboard.ID1"
'   - "Mouse.ID2"
'   - "USB Game Pad.ID3"
'   - "Saitek ST290 Pro.ID4"
'
' Therefore, depending on which id number a controller currently has will depend on which configuration file is used to load saved user defined
' buttons. This allows controllers to be set up with player 1-6 configurations with each player having a different preferred configuration.
'
' Also, controllers that have no user assigned buttons will still create a configuration file of zero bytes in size. This is normal.
'
' __LOAD_BUTTONS will look for configuration files associated with all currently connected controllers based on their id numbers. If a configration
' exists the user defined buttons wil be loaded.



' +---------------------------------------+
' |                                       |
' |   REMAPPING JOYSTICK/GAME PAD AXES    |
' |                                       |
' +---------------------------------------+
'
' As stated before, joystick and game pad axis are always returned as values between -1 and 1. The following function can be used to remap this range
' to a different value range:
'
' __MAP_AXIS(AxisValue, Lower, Upper)
'
' Example:
'
' J1Xaxis = __MAP_AXIS(__CONTROLLER_AXIS(__JOYPAD1CID, 1), -128, 128) ' remap joystick 1 horizontal axis values from -128 to 128 (0 being center)
' J1Yaxis = __MAP_AXIS(__CONTROLLER_AXIS(__JOYPAD1CID, 2), -128, 128) ' remap joystick 1 vertical axis values from -128 to 128   (0 being center)
' J2Xaxis = __MAP_AXIS(__CONTROLLER_AXIS(__JOYPAD2CID, 1), 0, 255) '    remap joystick 2 horizontal axis values from 0 to 255    (127 being center)
' J2Yaxis = __MAP_AXIS(__CONTROLLER_AXIS(__JOYPAD2CID, 2), 0, 255) '    remap joystick 2 vertical axis values from 0 to 255      (127 being center)
'
' The Lower and Upper range values can be any values you wish as long as the Lower value is less than the Upper value.



'
' +---------------------------------------+
' |                                       |
' |      LIBRARY CODE BEGINS HERE         |
' |                                       |
' +---------------------------------------+

'OPTION _EXPLICIT

' __________________________________________________________________________________________________________________________________________________
'/                                                                                                                   KEYBOARD KEY _BUTTON CONSTANTS \
CONST CLKEY_ESC = 2 '                                                                                                                               |
CONST CLKEY_F1 = 60 '              FUNCTION KEY ROW _BUTTON CONSTANTS                                                                               |
CONST CLKEY_F2 = 61 '               _____     _____ _____ _____ _____    _____ _____ _____ _____    _____ _____ _____ _____                         |
CONST CLKEY_F3 = 62 '              ||ESC||   ||F1 |||F2 |||F3 |||F4 ||  ||F5 |||F6 |||F7 |||F8 ||  ||F9 |||N/A|||F11|||F12||                        |
CONST CLKEY_F4 = 63 '              ||___||   ||___|||___|||___|||___||  ||___|||___|||___|||___||  ||___|||___|||___|||___||                        |
CONST CLKEY_F5 = 64 '              |/___\|   |/___\|/___\|/___\|/___\|  |/___\|/___\|/___\|/___\|  |/___\|/___\|/___\|/___\|                        |
CONST CLKEY_F6 = 65 '                                                                                                                               |
CONST CLKEY_F7 = 66 '              NOTE: F10 does not register as a _BUTTON. I know, strange but true.                                              |
CONST CLKEY_F8 = 67 '                    These _BUTTON contants were provided by gx.bi in dbox's Game Engine: https://github.com/boxgaming/gx       |
CONST CLKEY_F9 = 68 '                                                                                                                               |
CONST CLKEY_F11 = 88 '                                                                                                                              |
CONST CLKEY_F12 = 89 '                                                                                                                              |
CONST CLKEY_BACKQUOTE = 42 '       -----------------------------------------------------------------------------------------                        |
CONST CLKEY_1 = 3 '                FIRST KEY ROW _BUTTON CONSTANTS                                                                                  |
CONST CLKEY_2 = 4 '                                                                                                                                 |
CONST CLKEY_3 = 5 '                 _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _________                         |
CONST CLKEY_4 = 6 '                ||`~ |||1! |||2@ |||3# |||4$ |||5% |||6^ |||7& |||8* |||9( |||0) |||-_ |||=+ |||BACKSP ||                        |
CONST CLKEY_5 = 7 '                ||___|||___|||___|||___|||___|||___|||___|||___|||___|||___|||___|||___|||___|||_______||                        |
CONST CLKEY_6 = 8 '                |/___\|/___\|/___\|/___\|/___\|/___\|/___\|/___\|/___\|/___\|/___\|/___\|/___\|/_______\|                        |
CONST CLKEY_7 = 9 '                                                                                                                                 |
CONST CLKEY_8 = 10 '                                                                                                                                |
CONST CLKEY_9 = 11 '                                                                                                                                |
CONST CLKEY_0 = 12 '                                                                                                                                |
CONST CLKEY_MINUS = 13 '                                                                                                                            |
CONST CLKEY_EQUALS = 14 '                                                                                                                           |
CONST CLKEY_BACKSPACE = 15 '                                                                                                                        |
CONST CLKEY_TAB = 16 '             -----------------------------------------------------------------------------------------                        |
CONST CLKEY_Q = 17 '               SECOND KEY ROW _BUTTON CONSTANTS                                                                                 |
CONST CLKEY_W = 18 '                                                                                                                                |
CONST CLKEY_E = 19 '                _______ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _______                         |
CONST CLKEY_R = 20 '               ||TAB  |||Q  |||W  |||E  |||R  |||T  |||Y  |||U  |||I  |||O  |||P  |||[{ |||]} |||\|   ||                        |
CONST CLKEY_T = 21 '               ||_____|||___|||___|||___|||___|||___|||___|||___|||___|||___|||___|||___|||___|||_____||                        |
CONST CLKEY_Y = 22 '               |/_____\|/___\|/___\|/___\|/___\|/___\|/___\|/___\|/___\|/___\|/___\|/___\|/___\|/_____\|                        |
CONST CLKEY_U = 23 '                                                                                                                                |
CONST CLKEY_I = 24 '                                                                                                                                |
CONST CLKEY_O = 25 '                                                                                                                                |
CONST CLKEY_P = 26 '                                                                                                                                |
CONST CLKEY_LBRACKET = 27 '                                                                                                                         |
CONST CLKEY_RBRACKET = 28 '                                                                                                                         |
CONST CLKEY_BACKSLASH = 44 '                                                                                                                        |
CONST CLKEY_CAPSLOCK = 59 '        -----------------------------------------------------------------------------------------                        |
CONST CLKEY_A = 31 '               THIRD KEY ROW _BUTTON CONSTANTS                                                                                  |
CONST CLKEY_S = 32 '                                                                                                                                |
CONST CLKEY_D = 33 '                ________ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ ____________                         |
CONST CLKEY_F = 34 '               ||CAPS  |||A  |||S  |||D  |||F  |||G  |||H  |||J  |||K  |||L  |||;: |||'" |||ENTER     ||                        |
CONST CLKEY_G = 35 '               ||______|||___|||___|||___|||___|||___|||___|||___|||___|||___|||___|||___|||__________||                        |
CONST CLKEY_H = 36 '               |/______\|/___\|/___\|/___\|/___\|/___\|/___\|/___\|/___\|/___\|/___\|/___\|/__________\|                        |
CONST CLKEY_J = 37 '                                                                                                                                |
CONST CLKEY_K = 38 '                                                                                                                                |
CONST CLKEY_L = 39 '                                                                                                                                |
CONST CLKEY_SEMICOLON = 40 '                                                                                                                        |
CONST CLKEY_QUOTE = 41 '                                                                                                                            |
CONST CLKEY_ENTER = 29 '                                                                                                                            |
CONST CLKEY_LSHIFT = 43 '          -----------------------------------------------------------------------------------------                        |
CONST CLKEY_Z = 45 '               FOURTH KEY ROW _BUTTON CONSTANTS                                                                                 |
CONST CLKEY_X = 46 '                                                                                                                                |
CONST CLKEY_C = 47 '                _____________ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____ _____________                         |
CONST CLKEY_V = 48 '               ||LEFT SHIFT |||Z  |||X  |||C  |||V  |||B  |||N  |||M  |||,< |||.> |||/? |||RIGHT SHIFT||                        |
CONST CLKEY_B = 49 '               ||___________|||___|||___|||___|||___|||___|||___|||___|||___|||___|||___|||___________||                        |
CONST CLKEY_N = 50 '               |/___________\|/___\|/___\|/___\|/___\|/___\|/___\|/___\|/___\|/___\|/___\|/___________\|                        |
CONST CLKEY_M = 51 '                                                                                                                                |
CONST CLKEY_COMMA = 52 '                                                                                                                            |
CONST CLKEY_PERIOD = 53 '                                                                                                                           |
CONST CLKEY_SLASH = 54 '                                                                                                                            |
CONST CLKEY_RSHIFT = 55 '                                                                                                                           |
CONST CLKEY_LCTRL = 30 '           -----------------------------------------------------------------------------------------                        |
CONST CLKEY_LWIN = 348 '           FIFTH KEY ROW _BUTTON CONSTANTS                                                                                  |
CONST CLKEY_SPACEBAR = 58 '                                                                                                                         |
CONST CLKEY_RWIN = 349 '            ______ ______ ______ _____________________________________________ ______ ______ ______                         |
CONST CLKEY_RCTRL = 286 '          ||LCTL|||LWIN|||MENU|||SPACEBAR                                   |||N/A |||RWIN|||RCTL||                        |
CONST CLKEY_MENU = 350 '           ||____|||____|||____|||___________________________________________|||____|||____|||____||                        |
'                                  |/____\|/____\|/____\|/___________________________________________\|/____\|/____\|/____\|                        |
'                                                                                                                                                   |
CONST CLKEY_NUMLOCK = 326 '        -----------------------------------------------------------------------------------------                        |
CONST CLKEY_NUMPAD_DIVIDE = 310 '  NUMBER PAD _BUTTON CONSTANTS                                                                                     |
CONST CLKEY_NUMPAD_MULTIPLY = 56 '                                                                                                                  |
CONST CLKEY_NUMPAD_MINUS = 75 '     _____ _____ _____ _____                                                                                         |
CONST CLKEY_NUMPAD_7 = 72 '        ||NUM|||/  |||*  |||-  ||                                                                                        |
CONST CLKEY_NUMPAD_8 = 73 '        ||___|||___|||___|||___||                                                                                        |
CONST CLKEY_NUMPAD_9 = 74 '        |/___\|/___\|/___\|/___\|                                                                                        |
CONST CLKEY_NUMPAD_PLUS = 79 '      _____ _____ _____ _____                                                                                         |
CONST CLKEY_NUMPAD_4 = 76 '        ||7  |||8 |||9  |||+  ||                                                                                        |
CONST CLKEY_NUMPAD_5 = 77 '        ||___|||___|||___|||   ||                                                                                        |
CONST CLKEY_NUMPAD_6 = 78 '        |/___\|/___\|/___\||   ||                                                                                        |
CONST CLKEY_NUMPAD_1 = 80 '         _____ _____ _____||   ||                                                                                        |
CONST CLKEY_NUMPAD_2 = 81 '        ||4 |||5  |||6 |||   ||                                                                                        |
CONST CLKEY_NUMPAD_3 = 82 '        ||___|||___|||___|||___||                                                                                        |
CONST CLKEY_NUMPAD_ENTER = 285 '   |/___\|/___\|/___\|/___\|                                                                                        |
CONST CLKEY_NUMPAD_0 = 83 '         _____ _____ _____ _____                                                                                         |
CONST CLKEY_NUMPAD_PERIOD = 84 '   ||1  |||2 |||3  |||E  ||                                                                                        |
'                                  ||___|||___|||___|||N  ||                                                                                        |
'                                  |/___\|/___\|/___\||T  ||                                                                                        |
'                                   ___________ _____||E  ||                                                                                        |
'                                  ||0        |||.  |||R  ||                                                                                        |
'                                  ||_________|||___|||___||                                                                                        |
'                                  |/_________\|/___\|/___\|                                                                                        |
'                                                                                                                                                   |
CONST CLKEY_UP = 329 '             -----------------------------------------------------------------------------------------                        |
CONST CLKEY_LEFT = 332 '           ARROW KEY _BUTTON CONSTANTS                                                                                      |
CONST CLKEY_DOWN = 337 '                                                                                                                            |
CONST CLKEY_RIGHT = 334 '                 _____                                                                                                     |
'                                        ||  ||                                                                                                    |
'                                        ||___||                                                                                                    |
'                                        |/___\|                                                                                                    |
'                                   _____ _____ _____                                                                                               |
'                                  ||  |||  |||  ||                                                                                              |
'                                  ||___|||___|||___||                                                                                              |
'                                  |/___\|/___\|/___\|                                                                                              |
'                                                                                                                                                   |
CONST CLKEY_SCRLK = 71 '           -----------------------------------------------------------------------------------------                        |
CONST CLKEY_PAUSE = 70 '           POSITION KEY _BUTTON CONSTANTS                                                                                   |
CONST CLKEY_INSERT = 339 '                                                                                                                          |
CONST CLKEY_HOME = 328 '            _____ _____ _____                                                                                               |
CONST CLKEY_PAGEUP = 330 '         ||N/A|||SCR|||PAU||                                                                                              |
CONST CLKEY_DELETE = 340 '         ||___|||___|||___||               NOTE: Pause not working on my system?                                          |
CONST CLKEY_END = 336 '            |/___\|/___\|/___\|                                                                                              |
CONST CLKEY_PAGEDOWN = 338 '        _____ _____ _____|                                                                                              |
'                                  ||INS|||HOM|||PUP||                                                                                              |
'                                  ||___|||___|||___||                                                                                              |
'                                  |/___\|/___\|/___\|                                                                                              |
'                                   _____ _____ _____                                                                                               |
'                                  ||DEL|||END|||PDN||                                                                                              |
'                                  ||___|||___|||___||                                                                                              |
'                                  |/___\|/___\|/___\|                                                                                              |
'                                                                                                                                                   |
'\__________________________________________________________________________________________________________________________________________________/
'/                                                                                                                       __NEW_CONTROLLER CONSTANTS \
CONST __UNPLUGGED = 1 '                    existing controller unplugged        - use with __NEW_CONTROLLER                                         |
CONST __PLUGGEDIN = 2 '                    existing controller plugged back in  - use with __NEW_CONTROLLER                                         |
CONST __NEWCONTROLLER = 3 '                a new controller has been plugged in - use with __NEW_CONTROLLER                                         |
'\__________________________________________________________________________________________________________________________________________________/
'/                                                                                                                                       TYPE__SLOT \
TYPE TYPE__SLOT '                          USER DEFINED BUTTON SLOT PROPERTIES                                                                      |
    Cname AS STRING * 25 '                 controller name                                                                                          |
    cid AS INTEGER '                       controller id number (_DEVICES)                                                                          |
    Button AS INTEGER '                    button number (0 if using axis)                                                                          |
    Axis AS INTEGER '                      axis number (0 if using button) (- value for UP/LEFT or + value for DOWN/RIGHT)                          |
    Name AS STRING * 15 '                  button/axis name                                                                                         |
END TYPE '                                                                                                                                          |
'\__________________________________________________________________________________________________________________________________________________/
'/                                                                                                                                     TYPE__BUTTON \
TYPE TYPE__BUTTON '                        USER DEFINED BUTTON PROPERTIES                                                                           |
    Slot0 AS TYPE__SLOT '                  blank slot to clear others                                                                               |
    Slot1 AS TYPE__SLOT '                  user defined button slot 1                                                                               |
    Slot2 AS TYPE__SLOT '                  user defined button slot 2                                                                               |
    Slot3 AS TYPE__SLOT '                  user defined button slot 3                                                                               |
    Slot4 AS TYPE__SLOT '                  user defined button slot 4                                                                               |
END TYPE '                                                                                                                                          |
'\__________________________________________________________________________________________________________________________________________________/
'/                                                                                                                                 TYPE__CONTROLLER \
TYPE TYPE__CONTROLLER '                    DETECTED CONTROLLER PROPERTIES                                                                           |
    Found AS INTEGER '                     controller found (t/f)                                                                                   |
    Connected AS INTEGER '                 controller connected (t/f)                                                                               |
    Name AS STRING * 25 '                  description of controller                                                                                |
    Buttons AS INTEGER '                   number of buttons controller has ( _LASTBUTTON(Controller) )                                             |
    Axis AS INTEGER '                      number of axis controller has    ( _LASTAXIS(Controller)   )                                             |
    Wheels AS INTEGER '                    number of wheels controller has  ( _LASTWHEEL(Controller)  ) ** NOT IMPLEMENTED YET *                    |
END TYPE '                                                                                                                                          |
'\__________________________________________________________________________________________________________________________________________________/
'/                                                                                                                                   TYPE__SETTINGS \
TYPE TYPE__SETTINGS '                      LIBRARY SETTINGS                                                                                         |
    Reassign AS INTEGER '                  reassign (-1) or ignore (0) already used user defined buttons                                            |
    Threshold AS SINGLE '                  axis sensitivity when used as a user defined button (.01 to .99)                                         |
    FoundDevices AS INTEGER '              number of controllers found when program first started                                                   |
END TYPE '                                                                                                                                          |
'\__________________________________________________________________________________________________________________________________________________/
'/                                                                                                                             VARIABLE ASSIGNMENTS \
REDIM CL_BUTTON(1) AS TYPE__BUTTON '       user assigned button array                                                                               |
DIM CL_CONTROLLER(6) AS TYPE__CONTROLLER ' controller array (index number equals _DEVICES id number)                                                |
DIM CL_KEYNAME(350) AS STRING '            keyboard _BUTTON key names                                                                               |
DIM CL_SETTINGS AS TYPE__SETTINGS '        library settings                                                                                         |
DIM SHARED __CURRENT_ROUTINE AS STRING '   __ERROR use                                                                                              |
DIM SHARED __PREVIOUS_ROUTINE AS STRING '  __ERROR use                                                                                              |
DIM SHARED __KEYBOARDCID AS INTEGER '      these will contain the device ids (_DEVICES)                                                             |
DIM SHARED __MOUSECID AS INTEGER '                                                                                                                  |
DIM SHARED __JOYPAD1CID AS INTEGER '                                                                                                                |
DIM SHARED __JOYPAD2CID AS INTEGER '                                                                                                                |
DIM SHARED __JOYPAD3CID AS INTEGER '                                                                                                                |
DIM SHARED __JOYPAD4CID AS INTEGER '                                                                                                                |
DIM SHARED __JOYPAD5CID AS INTEGER '                                                                                                                |
DIM SHARED __JOYPAD6CID AS INTEGER '                                                                                                                |
'\__________________________________________________________________________________________________________________________________________________/


Code: (Select All)
'----------------------------
' Controller Library V1.10
' Terry Ritchie
' May 17th, 2023
' Written using QB64PE v3.7.0
'----------------------------
' CONTROLLER.BM
'----------------------------
'
' TODO: Add WHEEL routines
'       Create PDF instructions
'
'----------------------------
' 04/10/23 V1.0  - Initial Release
' 04/26/23 V1.01 - Corrected slot reassigning issues
' 05/17/23 V1.10 - Added __MAKE_BUTTON, __CONNECTED, __SAVE_BUTTONS, __LOAD_BUTTONS, __NEW_CONTROLLER, __REMOVE_CONTROLLER
'                - The library now has the ability to save and load controller user defined button configurations
'                - You can now detect when a controller has been plugged in or unplugged
'                - A controller's user defined button assignments can be removed when the controller has been unplugged
'----------------------------


' ______________________________________________________________________________________________________________________________________________
'/                                                                                                                                              \
SUB __REMOVE_CONTROLLER (cid AS INTEGER) '                                                                                  __REMOVE_CONTROLLER |
    ' __________________________________________________________________________________________________________________________________________|____
    '/                                                                                                                                               \
    '| Removes all user defined buttons associated with a controller.                                                                                |
    '|                                                                                                                                               |
    '| __REMOVE_CONTROLLER __JOYPAD3CID                                                                                                              |
    '|                                                                                                                                               |
    '| cid - the controller id                                                                                                                       |
    '\_______________________________________________________________________________________________________________________________________________/

    SHARED CL_CONTROLLER() AS TYPE__CONTROLLER ' need access to controller array
    SHARED CL_BUTTON() AS TYPE__BUTTON '         need access to button array
    DIM b AS INTEGER '                           button counter

    '+------------------+
    '| Check for errors |
    '+------------------+

    __CURRENT_ROUTINE = "__REMOVE_CONTROLLER"
    IF cid < 1 OR cid > 6 THEN __ERROR "Invalid controller id."
    IF CL_CONTROLLER(cid).Found = 0 THEN __ERROR "The specified controller does not exist."
    __PREVIOUS_ROUTINE = __CURRENT_ROUTINE

    '+-----------------------------------------------------------------------------+
    '| Cycle through each assigned button slot. If a button assignment matches the |
    '| controller name and controller id then remove the button from the array.    |
    '+-----------------------------------------------------------------------------+

    b = 0 '                                                                                           reset button counter
    DO '                                                                                              begin button search
        b = b + 1 '                                                                                   increment button counter
        IF CL_BUTTON(b).Slot1.Cname = CL_CONTROLLER(cid).Name AND CL_BUTTON(b).Slot1.cid = cid THEN ' does controller name and id match in slot 1?
            __REMOVE_BUTTON b, 1 '                                                                    yes, remove the button assignment in slot 1
        END IF
        IF CL_BUTTON(b).Slot2.Cname = CL_CONTROLLER(cid).Name AND CL_BUTTON(b).Slot2.cid = cid THEN ' does controller name and id match in slot 2?
            __REMOVE_BUTTON b, 2
        END IF
        IF CL_BUTTON(b).Slot3.Cname = CL_CONTROLLER(cid).Name AND CL_BUTTON(b).Slot3.cid = cid THEN ' does controller name and id match in slot 3?
            __REMOVE_BUTTON b, 3
        END IF
        IF CL_BUTTON(b).Slot4.Cname = CL_CONTROLLER(cid).Name AND CL_BUTTON(b).Slot4.cid = cid THEN ' does controller name and id match in slot 4?
            __REMOVE_BUTTON b, 4
        END IF
    LOOP UNTIL b = UBOUND(CL_BUTTON) '                                                                leave when all buttons checked

END SUB
' ______________________________________________________________________________________________________________________________________________
'/                                                                                                                                              \
FUNCTION __NEW_CONTROLLER (Action AS INTEGER) '                                                                                __NEW_CONTROLLER |
    ' __________________________________________________________________________________________________________________________________________|____
    '/                                                                                                                                               \
    '| Checks for the addition of a new controller, a controller that has been unplugged, or a controller plugged back in.                           |
    '|                                                                                                                                               |
    '| Controller = __NEW_CONTROLLER(Action)                                                                                                         |
    '|                                                                                                                                               |
    '| Action - the returned action that occurred                                                                                                    |
    '|      0 - nothing changed                                                                                                                      |
    '|      1 - a controller was unplugged           (the constant __UNPLUGGED     has been created to check for this)                               |
    '|      2 - a controller was plugged back in     (the constant __PLUGGEDIN     has been created to check for this)                               |
    '|      3 - a new controller has been plugged in (the constant __NEWCONTROLLER has been created to check for this)                               |
    '|                                                                                                                                               |
    '| The function will return the following values:                                                                                                |
    '|                                                                                                                                               |
    '|        - the value will contain the new controller id when a new controller is plugged in               (Action = 3     )                     |
    '|        - the value will contain the controller id of a controller that was unplugged or plugged back in (Action = 1 or 2)                     |
    '|        - a value of 0 (FALSE) if nothing has changed                                                                                          |
    '\_______________________________________________________________________________________________________________________________________________/

    SHARED CL_CONTROLLER() AS TYPE__CONTROLLER ' need access to controller array
    SHARED CL_SETTINGS AS TYPE__SETTINGS '       need access to library settings
    DIM Rescan AS INTEGER '                      -1 (TRUE) if controllers need to be rescanned
    DIM Devices AS INTEGER '                     number of devices currently found
    DIM d AS INTEGER '                           device counter

    __NEW_CONTROLLER = 0 '                                                    assume no new or previous controller plugged in
    Action = 0 '                                                              assume no changes
    d = 0 '                                                                   reset device counter
    Rescan = 0 '                                                              reset rescan flag
    Devices = _DEVICES '                                                      get number of devices found
    IF Devices <> CL_SETTINGS.FoundDevices THEN '                             has a new controller been plugged in?
        Rescan = -1 '                                                         yes, controllers will need to be rescanned
        Action = __NEWCONTROLLER '                                            remember that a new controller was plugged in
    ELSE '                                                                    no, check for previous plugged/unplugged controllers
        DO '                                                                  begin controller search
            d = d + 1 '                                                       increment device counter
            IF __CONNECTED(d) <> CL_CONTROLLER(d).Connected THEN '            has controller connection status changed?
                Rescan = d '                                                  yes, remember which controller has changed and needs rescanned
                IF CL_CONTROLLER(d).Connected THEN '                          was the controller connected?
                    Action = __UNPLUGGED '                                    yes, remember that it was just unplugged
                ELSE '                                                        no, the controller was disconnected
                    Action = __PLUGGEDIN '                                    remember that it was just plugged back in
                END IF
                EXIT DO '                                                     no need to check any more controllers
            END IF
        LOOP UNTIL d = Devices '                                              leave when all controllers checked
    END IF
    IF Rescan THEN '                                                          need to scan for new/unplugged/plugged in controllers?
        __IDENTIFY_CONTROLLERS '                                              yes, identify connected controllers
        IF Rescan = -1 THEN '                                                 was a new controller found?
            __NEW_CONTROLLER = CL_SETTINGS.FoundDevices '                     yes, return the new controller id number
        ELSE '                                                                no, a controller was unplugged/plugged back in
            __NEW_CONTROLLER = Rescan '                                       return the controller that was unplugged/plugged back in
        END IF
    END IF

END FUNCTION
' ______________________________________________________________________________________________________________________________________________
'/                                                                                                                                              \
SUB __MAKE_BUTTON (Handle AS INTEGER) '                                                                                           __MAKE_BUTTON |
    ' __________________________________________________________________________________________________________________________________________|____
    '/                                                                                                                                               \
    '| Assigns a value to a user generated integer button variable.                                                                                  |
    '|                                                                                                                                               |
    '| DIM UPButton AS INTEGER                                                                                                                       |
    '| __MAKE_BUTTON UPButton ' define the integer variable UPButton as a user defined button                                                        |
    '|                                                                                                                                               |
    '| Handle - the name of the variable the user wishes to use as a button reference.                                                               |
    '|          The variable's value will change to indicate the new handle value pointing to the button array index.                                |
    '\_______________________________________________________________________________________________________________________________________________/

    SHARED CL_BUTTON() AS TYPE__BUTTON ' need access to button array

    '+------------------+
    '| Check for errors |
    '+------------------+

    __CURRENT_ROUTINE = "__MAKE_BUTTON"
    IF Handle THEN __ERROR "This button has already been created"
    __PREVIOUS_ROUTINE = __CURRENT_ROUTINE

    '+----------------------------------------------+
    '| Create a new entry for the button assignment |
    '+----------------------------------------------+

    Handle = UBOUND(CL_BUTTON) + 1 '                    set handle to new size of button array
    REDIM _PRESERVE CL_BUTTON(Handle) AS TYPE__BUTTON ' increase size of button array to match new handle

END SUB
' ______________________________________________________________________________________________________________________________________________
'/                                                                                                                                              \
SUB __SAVE_BUTTONS () '                                                                                                          __SAVE_BUTTONS |
    ' __________________________________________________________________________________________________________________________________________|____
    '/                                                                                                                                               \
    '| Saves all assigned buttons for all currently discovered and connected controllers.                                                            |
    '|                                                                                                                                               |
    '| __SAVE_BUTTONS                                                                                                                                |
    '|                                                                                                                                               |
    '| The config files created for the controllers will be in the form: [Controller Name].ID[Device ID#]                                            |
    '| For example, a joystick with the name Saitek ST290 Pro found as device number 4 will have the following config file: "Saitek ST290 Pro.ID4"   |
    '|                                                                                                                                               |
    '| It's possible for a controller to have multiple config files based on the device id number.                                                   |
    '|   - "Saitek ST290 Pro.ID3"                                                                                                                    |
    '|   - "Saitek ST290 Pro.ID4"                                                                                                                    |
    '|   - "Saitek ST290 Pro.ID5"                                                                                                                    |
    '|   - etc..                                                                                                                                     |
    '|                                                                                                                                               |
    '| This allows for multiple configurations based on which player is using which joystick/game pad in any order.                                  |
    '|                                                                                                                                               |
    '| Controllers with no assigned buttons will create config files that are zero bytes in length. This is normal.                                  |
    '\_______________________________________________________________________________________________________________________________________________/

    SHARED CL_CONTROLLER() AS TYPE__CONTROLLER ' need access to controller array
    SHARED CL_BUTTON() AS TYPE__BUTTON '         need access to button array
    DIM Fname AS STRING '                        controller config file name
    DIM b AS INTEGER '                           button counter
    DIM cid AS INTEGER '                         controller id counter
    DIM FF AS LONG '                             next available free file handle

    cid = 0 '                                                                                                     reset controller id counter
    DO '                                                                                                          begin controller search loop
        cid = cid + 1 '                                                                                           increment controller id counter
        IF CL_CONTROLLER(cid).Found THEN '                                                                        was this controller found initially?
            IF __CONNECTED(cid) THEN '                                                                            yes, is this controller still connected?

                '+---------------------------------------------------+
                '| This controller is in use and currently connected |
                '| Create and open a config file for this controller |
                '+---------------------------------------------------+

                Fname = _TRIM$(CL_CONTROLLER(cid).Name) + ".ID" + _TRIM$(STR$(cid)) '                             yes, build the name of controller's config file
                FF = FREEFILE '                                                                                   get a free file handle
                OPEN Fname FOR OUTPUT AS #FF '                                                                    open the config file for writing
                b = 0 '                                                                                           reset button counter

                '+-----------------------------------------------------------------------------+
                '| Cycle through each assigned button slot. If a button assignment matches the |
                '| controller name and controller id save the variable assignment value, the   |
                '| button value (0 if axis is used), and the axis value (0 if button is used). |
                '+-----------------------------------------------------------------------------+

                DO '                                                                                              begin assignment search and write loop
                    b = b + 1 '                                                                                   increment button counter
                    IF CL_BUTTON(b).Slot1.Cname = CL_CONTROLLER(cid).Name AND CL_BUTTON(b).Slot1.cid = cid THEN ' does controller name and id match in slot 1?
                        WRITE #FF, b, CL_BUTTON(b).Slot1.Button, CL_BUTTON(b).Slot1.Axis '                        yes, write assignment to file
                    END IF
                    IF CL_BUTTON(b).Slot2.Cname = CL_CONTROLLER(cid).Name AND CL_BUTTON(b).Slot2.cid = cid THEN ' does controller name and id match in slot 2?
                        WRITE #FF, b, CL_BUTTON(b).Slot2.Button, CL_BUTTON(b).Slot2.Axis '                        yes, write assignment to file
                    END IF
                    IF CL_BUTTON(b).Slot3.Cname = CL_CONTROLLER(cid).Name AND CL_BUTTON(b).Slot3.cid = cid THEN ' does controller name and id match in slot 3?
                        WRITE #FF, b, CL_BUTTON(b).Slot3.Button, CL_BUTTON(b).Slot3.Axis '                        yes, write assignment to file
                    END IF
                    IF CL_BUTTON(b).Slot4.Cname = CL_CONTROLLER(cid).Name AND CL_BUTTON(b).Slot4.cid = cid THEN ' does controller name and id match in slot 4?
                        WRITE #FF, b, CL_BUTTON(b).Slot4.Button, CL_BUTTON(b).Slot4.Axis '                        yes, write assignment to file
                    END IF
                LOOP UNTIL b = UBOUND(CL_BUTTON) '                                                                leave when all button assignments checked
                CLOSE #FF '                                                                                       close the file
            END IF
        END IF
    LOOP UNTIL cid = 6 '                                                                                          leave when all controllers checked
END SUB
' ______________________________________________________________________________________________________________________________________________
'/                                                                                                                                              \
SUB __LOAD_BUTTONS () '                                                                                                          __LOAD_BUTTONS |
    ' __________________________________________________________________________________________________________________________________________|____
    '/                                                                                                                                               \
    '| Loads assigned buttons for controllers from configuration files if they exist.                                                                |
    '|                                                                                                                                               |
    '| __LOAD_BUTTONS                                                                                                                                |
    '\_______________________________________________________________________________________________________________________________________________/

    SHARED CL_CONTROLLER() AS TYPE__CONTROLLER ' need access to controller array
    SHARED CL_BUTTON() AS TYPE__BUTTON '         need access to button array
    DIM Fname AS STRING '                        controller config file name
    DIM cid AS INTEGER '                         controller id counter
    DIM Handle AS INTEGER '                      user defined button handle
    DIM Button AS INTEGER '                      button to assign (0 if axis)
    DIM Axis AS INTEGER '                        axis to assign (0 if button)
    DIM FF AS LONG '                             next available free file handle

    cid = 0 '                                                                         reset controller id counter
    DO '                                                                              begin controller search loop
        cid = cid + 1 '                                                               increment controller id counter
        IF CL_CONTROLLER(cid).Found THEN '                                            was this controller found initially?
            IF __CONNECTED(cid) THEN '                                                yes, is this controller still connected?

                '+-----------------------------------------------------------------+
                '| This controller was initially found and is currently connected. |
                '| Create a config file name to check for.                         |
                '+-----------------------------------------------------------------+

                Fname = _TRIM$(CL_CONTROLLER(cid).Name) + ".ID" + _TRIM$(STR$(cid)) ' yes, build the name of the controller's config file
                IF _FILEEXISTS(Fname) THEN '                                          does a config file for this controller exist?

                    '+-------------------------------------------+
                    '| A config file exists for this controller  |
                    '| Open the file and assign the buttons/axes |
                    '+-------------------------------------------+

                    FF = FREEFILE '                                                   yes, get a free file handle
                    OPEN Fname FOR INPUT AS #FF '                                     open the config file for reading
                    WHILE NOT EOF(1) '                                                has the end of the file been reached?

                        '+---------------------------------------------------------------+
                        '| Config files that are zero bytes in length are simply ignored |
                        '+---------------------------------------------------------------+

                        INPUT #FF, Handle, Button, Axis '                             no, get the button handle, button, and axis settings
                        IF Button THEN '                                              has a button been assigned?
                            __ASSIGN_BUTTON Handle, cid, Button '                     yes, assign the button
                        ELSE '                                                        no, an axis is assigned
                            __ASSIGN_AXIS Handle, cid, Axis '                         assign the axis as a button
                        END IF
                    WEND '                                                            loop back and load next button assignment (if any)
                    CLOSE #FF '                                                       close the config file
                END IF
            END IF
        END IF
    LOOP UNTIL cid = 6 '                                                              leave when all controllers searched

END SUB
' ______________________________________________________________________________________________________________________________________________
'/                                                                                                                                              \
FUNCTION __CONNECTED (cid AS INTEGER) '                                                                                             __CONNECTED |
    ' __________________________________________________________________________________________________________________________________________|____
    '/                                                                                                                                               \
    '| Returns 0 (FALSE) if a controller is disconnected, -1 (TRUE) otherwise                                                                        |
    '|                                                                                                                                               |
    '| Status = __CONNECTED(__JOYPAD1CID)                                                                                                            |
    '|                                                                                                                                               |
    '| cid - the id of the controller                                                                                                                |
    '\_______________________________________________________________________________________________________________________________________________/

    SHARED CL_CONTROLLER() AS TYPE__CONTROLLER ' need access to controller array

    '+------------------+
    '| Check for errors |
    '+------------------+

    __CURRENT_ROUTINE = "__CONNECTED"
    IF cid < 1 OR cid > 6 THEN __ERROR "Invalid controller id."
    IF CL_CONTROLLER(cid).Found = 0 THEN __ERROR "The specified controller does not exist."
    __PREVIOUS_ROUTINE = __CURRENT_ROUTINE

    '+-------------------------+
    '| Return connection state |
    '+-------------------------+

    WHILE _DEVICEINPUT(cid): WEND '                  get latest controller values
    IF INSTR(_DEVICE$(cid), "[DISCONNECTED]") THEN ' is controller disconnected?
        __CONNECTED = 0 '                            yes, return that controller is disconnected
    ELSE '                                           no
        __CONNECTED = -1 '                           return that controller is connected
    END IF

END FUNCTION
' ______________________________________________________________________________________________________________________________________________
'/                                                                                                                                              \
FUNCTION __BUTTON_NAME$ (Handle AS INTEGER, Slot AS INTEGER) '                                                                   __BUTTON_NAME$ |
    ' __________________________________________________________________________________________________________________________________________|____
    '/                                                                                                                                               \
    '| Retrieves the name of a user defined button.                                                                                                  |
    '|                                                                                                                                               |
    '| Description = __BUTTON_NAME$(Up_Button, 1) ' get name of button in slot 1                                                                     |
    '|                                                                                                                                               |
    '| Handle - the handle of the user defined button                                                                                                |
    '| Slot   - the slot number (1 to 4)                                                                                                             |
    '|          passing the value of 0 will clear all slot assignments and remove the user defined button completely.                                |
    '\_______________________________________________________________________________________________________________________________________________/

    SHARED CL_BUTTON() AS TYPE__BUTTON '         need access to button array

    '+------------------+
    '| Check for errors |
    '+------------------+

    __CURRENT_ROUTINE = "__BUTTON_NAME$"
    IF Handle < 1 OR Handle > UBOUND(CL_BUTTON) THEN __ERROR "The specified button does not exist."
    IF Slot < 0 OR Slot > 4 THEN __ERROR "The requested slot assignment does not exist."
    __PREVIOUS_ROUTINE = __CURRENT_ROUTINE

    '+-----------------+
    '| Get button name |
    '+-----------------+

    SELECT CASE Slot
        CASE 1: __BUTTON_NAME$ = CL_BUTTON(Handle).Slot1.Name
        CASE 2: __BUTTON_NAME$ = CL_BUTTON(Handle).Slot2.Name
        CASE 3: __BUTTON_NAME$ = CL_BUTTON(Handle).Slot3.Name
        CASE 4: __BUTTON_NAME$ = CL_BUTTON(Handle).Slot4.Name
    END SELECT

END FUNCTION
' ______________________________________________________________________________________________________________________________________________
'/                                                                                                                                              \
SUB __SET_AXIS_THRESHOLD (Value AS SINGLE) '                                                                               __SET_AXIS_THRESHOLD |
    ' __________________________________________________________________________________________________________________________________________|____
    '/                                                                                                                                               \
    '| Sets the value at which an axis set up as a user defined button is seen as being pressed.                                                     |
    '|                                                                                                                                               |
    '| __SET_AXIS_THRESHOLD .25 ' button activated when axis is deflected 25% either UP/DOWN or LEFT/RIGHT                                           |
    '|                                                                                                                                               |
    '| Value - .01 (1%) to .99 (99%) of axis deflection                                                                                              |
    '|                                                                                                                                               |
    '| NOTE: The default value set up by __INITIALIZE_CONTROLLERS is .5 (50%)                                                                        |
    '\_______________________________________________________________________________________________________________________________________________/

    SHARED CL_SETTINGS AS TYPE__SETTINGS ' need access to library settings

    '+------------------+
    '| Check for errors |
    '+------------------+

    __CURRENT_ROUTINE = "__SET_AXIS_THRESHOLD"
    IF Value <= 0 OR Value >= 1 THEN __ERROR "Threshold value must be between 0 and 1."
    __PREVIOUS_ROUTINE = __CURRENT_ROUTINE

    '+---------------------+
    '| Set threshold value |
    '+---------------------+

    CL_SETTINGS.Threshold = Value ' set value of user defined button axis sensitivity

END SUB
' ______________________________________________________________________________________________________________________________________________
'/                                                                                                                                              \
SUB __BUTTON_REASSIGN_ALLOWED () '                                                                                    __BUTTON_REASSIGN_ALLOWED |
    ' __________________________________________________________________________________________________________________________________________|____
    '/                                                                                                                                               \
    '| Enables user defined button reassigning.                                                                                                      |
    '|                                                                                                                                               |
    '| __BUTTON_REASSIGN_ALLOWED                                                                                                                     |
    '\_______________________________________________________________________________________________________________________________________________/

    SHARED CL_SETTINGS AS TYPE__SETTINGS ' need access to library settings

    '+---------------------------+
    '| Enable button reassigning |
    '+---------------------------+

    CL_SETTINGS.Reassign = -1 ' enable button reassigning (TRUE)

END SUB
' ______________________________________________________________________________________________________________________________________________
'/                                                                                                                                              \
SUB __BUTTON_REASSIGN_NOT_ALLOWED () '                                                                            __BUTTON_REASSIGN_NOT_ALLOWED |
    ' __________________________________________________________________________________________________________________________________________|____
    '/                                                                                                                                               \
    '| Disables user defined button reassigning.                                                                                                     |
    '|                                                                                                                                               |
    '| __BUTTON_REASSIGN_NOT_ALLOWED                                                                                                                 |
    '\_______________________________________________________________________________________________________________________________________________/

    SHARED CL_SETTINGS AS TYPE__SETTINGS ' need access to library settings

    '+----------------------------+
    '| Disable button reassigning |
    '+----------------------------+

    CL_SETTINGS.Reassign = 0 ' disable button reassigning (FALSE)

END SUB
' ______________________________________________________________________________________________________________________________________________
'/                                                                                                                                              \
SUB __REMOVE_BUTTON (Handle AS INTEGER, Slot AS INTEGER) '                                                                      __REMOVE_BUTTON |
    ' __________________________________________________________________________________________________________________________________________|____
    '/                                                                                                                                               \
    '| Removes an assigned button or axis from a user defined button. Optionally the entire user defined button can be removed by supplying the      |
    '| value of 0 for slot.                                                                                                                          |
    '|                                                                                                                                               |
    '| __REMOVE_BUTTON UP_Button, 3 ' remove the button/axis assigned in the third slot                                                              |
    '|                                                                                                                                               |
    '| Handle - the handle of the user defined button                                                                                                |
    '| Slot   - the assigned slot to clear (0 to 4)                                                                                                  |
    '|          passing the value of 0 will clear all slot assignments and remove the user defined button completely.                                |
    '|                                                                                                                                               |
    '| When a defined button is removed from a slot the assignments in slots above are shifted down. For example, if the assignment in slot 1 is     |
    '| removed then the assignment in 2 is shifted to 1, 3 is shifted to 2, 4 is shifted to 3, and 4 is cleared.                                     |
    '\_______________________________________________________________________________________________________________________________________________/

    SHARED CL_BUTTON() AS TYPE__BUTTON ' need access to button array

    '+------------------+
    '| Check for errors |
    '+------------------+

    __CURRENT_ROUTINE = "__REMOVE_BUTTON"
    IF Handle < 1 OR Handle > UBOUND(CL_BUTTON) THEN __ERROR "The specified button does not exist."
    IF Slot < 0 OR Slot > 4 THEN __ERROR "The requested slot assignment does not exist."
    __PREVIOUS_ROUTINE = __CURRENT_ROUTINE

    '+----------------------+
    '| Remove assignment(s) |
    '+----------------------+

    SELECT CASE Slot '                                          which slot?
        CASE 0 '                                                [ALL SLOTS]
            CL_BUTTON(Handle).Slot1 = CL_BUTTON(Handle).Slot0 ' clear slot 1
            CL_BUTTON(Handle).Slot2 = CL_BUTTON(Handle).Slot0 ' clear slot 2
            CL_BUTTON(Handle).Slot3 = CL_BUTTON(Handle).Slot0 ' clear slot 3
            CL_BUTTON(Handle).Slot4 = CL_BUTTON(Handle).Slot0 ' clear slot 4
        CASE 1 '                                                [SLOT1]
            CL_BUTTON(Handle).Slot1 = CL_BUTTON(Handle).Slot2 ' move slot 2 up to slot 1
            CL_BUTTON(Handle).Slot2 = CL_BUTTON(Handle).Slot3 ' move slot 3 up to slot 2
            CL_BUTTON(Handle).Slot3 = CL_BUTTON(Handle).Slot4 ' move slot 4 up to slot 3
            CL_BUTTON(Handle).Slot4 = CL_BUTTON(Handle).Slot0 ' clear slot 4
        CASE 2 '                                                [SLOT2]
            CL_BUTTON(Handle).Slot2 = CL_BUTTON(Handle).Slot3 ' move slot 3 up to slot 2
            CL_BUTTON(Handle).Slot3 = CL_BUTTON(Handle).Slot4 ' move slot 4 up to slot 3
            CL_BUTTON(Handle).Slot4 = CL_BUTTON(Handle).Slot0 ' clear slot 4
        CASE 3 '                                                [SLOT3]
            CL_BUTTON(Handle).Slot3 = CL_BUTTON(Handle).Slot4 ' move slot 4 up to slot 3
            CL_BUTTON(Handle).Slot4 = CL_BUTTON(Handle).Slot0 ' clear slot 4
        CASE 4 '                                                [SLOT4]
            CL_BUTTON(Handle).Slot4 = CL_BUTTON(Handle).Slot0 ' clear slot 4
    END SELECT

END SUB
' ______________________________________________________________________________________________________________________________________________
'/                                                                                                                                              \
FUNCTION __JOYPAD_EXISTS (Number AS INTEGER) '                                                                                  __JOYPAD_EXISTS |
    ' __________________________________________________________________________________________________________________________________________|____
    '/                                                                                                                                               \
    '| Returns the total number of joysticks/game pads that exist (TREU) if the selected joystick/game pad exists, 0 (FALSE) otherwise.              |
    '|                                                                                                                                               |
    '| JoyPads = __JOYPAD_EXISTS(1) '                                     get total number of joysticks/game pads (if any)                           |
    '| IF JoyPads THEN '                                                  was the selected joypad found?                                             |
    '|     PRINT "Joystick 1 of"; STR$(JoyPads); " found!" '              yes, report findings                                                       |
    '|     Print "Joystick name    : ";__CONTROLLER_NAME$(__JOYPAD1CID)                                                                              |
    '|     PRINT "Number of buttons:"; __BUTTON_TOTAL(__JOYPAD1CID)                                                                                  |
    '|     PRINT "Number of axes   :"; __AXIS_TOTAL(__JOYPAD1CID)                                                                                    |
    '| END IF                                                                                                                                        |
    '|                                                                                                                                               |
    '| number - the joystick/game pad to query                                                                                                       |
    '\_______________________________________________________________________________________________________________________________________________/

    DIM Found AS INTEGER ' joypad found (t/f)

    '+------------------+
    '| Check for errors |
    '+------------------+

    __CURRENT_ROUTINE = "__JOYPAD_EXISTS"
    IF Number < 1 OR Number > 6 THEN __ERROR "The requested joystick/game pad does not exist."
    __PREVIOUS_ROUTINE = __CURRENT_ROUTINE

    '+---------------------------------------+
    '| Report existance of joystick/game pad |
    '+---------------------------------------+

    Found = 0 '                                   assume no joystick/game pad found
    __JOYPAD_EXISTS = 0 '                         assume there are no joypads
    SELECT CASE Number '                          which joypad are we looking for?
        CASE 1 '                                  joypad 1
            IF __JOYPAD1CID THEN Found = -1 '     record found if present
        CASE 2 '                                  etc..
            IF __JOYPAD2CID THEN Found = -1
        CASE 3
            IF __JOYPAD3CID THEN Found = -1
        CASE 4
            IF __JOYPAD4CID THEN Found = -1
        CASE 5
            IF __JOYPAD5CID THEN Found = -1
        CASE 6
            IF __JOYPAD6CID THEN Found = -1
    END SELECT
    IF Found THEN '                               was a joypad found?
        IF __JOYPAD6CID THEN '                    yes, are there 6 joypads?
            __JOYPAD_EXISTS = 6 '                 yes, return that 6 exist
        ELSEIF __JOYPAD5CID THEN '                no, are there 5 joypads?
            __JOYPAD_EXISTS = 5 '                 yes, return that 5 exist
        ELSEIF __JOYPAD4CID THEN '                etc..
            __JOYPAD_EXISTS = 4
        ELSEIF __JOYPAD3CID THEN
            __JOYPAD_EXISTS = 3
        ELSEIF __JOYPAD2CID THEN
            __JOYPAD_EXISTS = 2
        ELSEIF __JOYPAD1CID THEN
            __JOYPAD_EXISTS = 1
        END IF
    END IF

END FUNCTION
' ______________________________________________________________________________________________________________________________________________
'/                                                                                                                                              \
FUNCTION __MOUSE_EXISTS () '                                                                                                     __MOUSE_EXISTS |
    ' __________________________________________________________________________________________________________________________________________|____
    '/                                                                                                                                               \
    '| Returns -1 (TRUE) if the mouse controller exists, 0 (FALSE) otherwise.                                                                        |
    '|                                                                                                                                               |
    '| IF __MOUSE_EXISTS THEN PRINT "Mouse found!"                                                                                                   |
    '|                                                                                                                                               |
    '| NOTE: It's highly unlikely that a mouse will not exist but for those instances where someone may have created a stand alone computer for      |
    '|       playing QB64 games without a mouse (and/or a keyboard) but just joysticks attached this function is available.                          |
    '\_______________________________________________________________________________________________________________________________________________/

    __MOUSE_EXISTS = 0 '                     assume no mouse
    IF __MOUSECID THEN __MOUSE_EXISTS = -1 ' report that mouse found

END FUNCTION
' ______________________________________________________________________________________________________________________________________________
'/                                                                                                                                              \
FUNCTION __KEYBOARD_EXISTS () '                                                                                               __KEYBOARD_EXISTS |
    ' __________________________________________________________________________________________________________________________________________|____
    '/                                                                                                                                               \
    '| Returns -1 (TRUE) if the keyboard controller exists, 0 (FALSE) otherwise.                                                                     |
    '|                                                                                                                                               |
    '| IF __KEYBOARD_EXISTS THEN PRINT "Keyboard found!"                                                                                             |
    '|                                                                                                                                               |
    '| NOTE: It's highly unlikely that a keyboard will not exist but for those instances where someone may have created a stand alone computer for   |
    '|       playing QB64 games without a keyboard (and/or a mouse) but just joysticks attached this function is available.                          |
    '\_______________________________________________________________________________________________________________________________________________/

    __KEYBOARD_EXISTS = 0 '                        assume no keyboard
    IF __KEYBOARDCID THEN __KEYBOARD_EXISTS = -1 ' report that keyboard was found

END FUNCTION
' ______________________________________________________________________________________________________________________________________________
'/                                                                                                                                              \
FUNCTION __MAP_AXIS (AxisValue AS SINGLE, Lower AS INTEGER, Upper AS INTEGER) '                                                      __MAP_AXIS |
    ' __________________________________________________________________________________________________________________________________________|____
    '/                                                                                                                                               \
    '| Maps a joypad or mouse axis value from -1 to 1 to another defined range.                                                                      |
    '|                                                                                                                                               |
    '| Xaxis = __MAP_AXIS(__CONTROLLER_AXIS(__JOYPAD1CID, 1), 0, 255) ' map -1 to 1 as 0 to 255                                                      |
    '|                                                                                                                                               |
    '| AxisValue - current axis value (must be -1 to 1)                                                                                              |
    '| Lower     - the new lower value range                                                                                                         |
    '| Upper     - the new upper value range                                                                                                         |
    '|                                                                                                                                               |
    '| NOTE: This function will only work correctly when AxisValue is between -1 and 1.                                                              |
    '\_______________________________________________________________________________________________________________________________________________/

    '+------------------+
    '| Check for errors |
    '+------------------+

    __CURRENT_ROUTINE = "__MAP_AXIS"
    IF AxisValue < -1 OR AxisValue > 1 THEN __ERROR "Axis value must be between -1 and 1."
    IF Lower >= Upper THEN __ERROR "The lower value must be less than the upper value."
    __PREVIOUS_ROUTINE = __CURRENT_ROUTINE

    '+-----------------------------------+
    '| Convert input to new output range |
    '+-----------------------------------+

    __MAP_AXIS = INT(Lower + (AxisValue + 1) * (Lower - Upper) / -2) ' convert input to adjusted output

END FUNCTION
' ______________________________________________________________________________________________________________________________________________
'/                                                                                                                                              \
FUNCTION __AXIS_TOTAL (cid AS INTEGER) '                                                                                           __AXIS_TOTAL |
    ' __________________________________________________________________________________________________________________________________________|____
    '/                                                                                                                                               \
    '| Returns the number of axes a controller has.                                                                                                  |
    '|                                                                                                                                               |
    '| Total = __AXIS_TOTAL(__JOYPAD1CID)                                                                                                            |
    '|                                                                                                                                               |
    '| cid - the controller id                                                                                                                       |
    '\_______________________________________________________________________________________________________________________________________________/

    SHARED CL_CONTROLLER() AS TYPE__CONTROLLER ' need access to controller array

    '+------------------+
    '| Check for errors |
    '+------------------+

    __CURRENT_ROUTINE = "__AXIS_TOTAL"
    IF cid < 1 OR cid > 6 THEN __ERROR "Invalid controller id."
    IF CL_CONTROLLER(cid).Found = 0 THEN __ERROR "The specified controller does not exist."
    IF CL_CONTROLLER(cid).Axis = 0 THEN __ERROR "The specified controller has no axes."
    __PREVIOUS_ROUTINE = __CURRENT_ROUTINE

    '+----------------------------------+
    '| Return number of controller axis |
    '+----------------------------------+

    __AXIS_TOTAL = CL_CONTROLLER(cid).Axis ' return number of axes

END FUNCTION
' ______________________________________________________________________________________________________________________________________________
'/                                                                                                                                              \
FUNCTION __BUTTON_TOTAL (cid AS INTEGER) '                                                                                       __BUTTON_TOTAL |
    ' __________________________________________________________________________________________________________________________________________|____
    '/                                                                                                                                               \
    '| Returns the number of buttons a controller has.                                                                                               |
    '|                                                                                                                                               |
    '| Total = __BUTTON_TOTAL(__JOYPAD1CID)                                                                                                          |
    '|                                                                                                                                               |
    '| cid - the controller id                                                                                                                       |
    '\_______________________________________________________________________________________________________________________________________________/

    SHARED CL_CONTROLLER() AS TYPE__CONTROLLER ' need access to controller array

    '+------------------+
    '| Check for errors |
    '+------------------+

    __CURRENT_ROUTINE = "__BUTTON_TOTAL"
    IF cid < 1 OR cid > 6 THEN __ERROR "Invalid controller id."
    IF CL_CONTROLLER(cid).Found = 0 THEN __ERROR "The specified controller does not exist."
    IF CL_CONTROLLER(cid).Buttons = 0 THEN __ERROR "The specified controller has no buttons."
    __PREVIOUS_ROUTINE = __CURRENT_ROUTINE

    '+-------------------------------------+
    '| Return number of controller buttons |
    '+-------------------------------------+

    __BUTTON_TOTAL = CL_CONTROLLER(cid).Buttons ' return number of buttons

END FUNCTION
' ______________________________________________________________________________________________________________________________________________
'/                                                                                                                                              \
FUNCTION __CONTROLLER_BUTTON (cid AS INTEGER, Button AS INTEGER) '                                                          __CONTROLLER_BUTTON |
    ' __________________________________________________________________________________________________________________________________________|____
    '/                                                                                                                                               \
    '| Returns the state of a controller's button.                                                                                                   |
    '|                                                                                                                                               |
    '| Button = __CONTROLLER_BUTTON(__KEYBOARDCID, 329) ' keyboard UP arrow key                                                                      |
    '|                                                                                                                                               |
    '| cid    - the controller id                                                                                                                    |
    '| Button - the controller's button (or keyboard key)                                                                                            |
    '\_______________________________________________________________________________________________________________________________________________/

    SHARED CL_CONTROLLER() AS TYPE__CONTROLLER ' need access to controller array

    '+------------------+
    '| Check for errors |
    '+------------------+

    __CURRENT_ROUTINE = "__CONTROLLER_BUTTON"
    IF cid < 1 OR cid > 6 THEN __ERROR "Invalid controller id."
    IF CL_CONTROLLER(cid).Found = 0 THEN __ERROR "The specified controller does not exist."
    IF CL_CONTROLLER(cid).Buttons = 0 THEN __ERROR "The specified controller has no buttons."
    IF Button < 1 OR Button > CL_CONTROLLER(cid).Buttons THEN __ERROR "The specified controller does not have this button."
    __PREVIOUS_ROUTINE = __CURRENT_ROUTINE

    '+-----------------------------+
    '| Return current button state |
    '+-----------------------------+

    IF __CONNECTED(cid) THEN '                  is controller connected?
        WHILE _DEVICEINPUT(cid): WEND '         yes, get latest controller values
        __CONTROLLER_BUTTON = _BUTTON(Button) ' return controller button state
    END IF

END FUNCTION
' ______________________________________________________________________________________________________________________________________________
'/                                                                                                                                              \
FUNCTION __CONTROLLER_AXIS (cid AS INTEGER, Axis AS INTEGER) '                                                                __CONTROLLER_AXIS |
    ' __________________________________________________________________________________________________________________________________________|____
    '/                                                                                                                                               \
    '| Returns the value of a controller's axis. (-1 to 1)                                                                                           |
    '|                                                                                                                                               |
    '| Xaxis = __CONTROLLER_AXIS(__JOYPAD1CID, 1) ' x axis of joypad 1                                                                               |
    '|                                                                                                                                               |
    '| cid  - the controller id                                                                                                                      |
    '| Axis - the controller's axis number                                                                                                           |
    '\_______________________________________________________________________________________________________________________________________________/

    SHARED CL_CONTROLLER() AS TYPE__CONTROLLER ' need access to controller array

    '+------------------+
    '| Check for errors |
    '+------------------+

    __CURRENT_ROUTINE = "__CONTROLLER_AXIS"
    IF cid < 1 OR cid > 6 THEN __ERROR "Invalid controller id."
    IF CL_CONTROLLER(cid).Found = 0 THEN __ERROR "The specified controller does not exist."
    IF CL_CONTROLLER(cid).Axis = 0 THEN __ERROR "The specified controller has no axes."
    IF Axis < 1 OR Axis > CL_CONTROLLER(cid).Axis THEN __ERROR "The specified controller does not have this axis."
    __PREVIOUS_ROUTINE = __CURRENT_ROUTINE

    '+---------------------------+
    '| Return current axis value |
    '+---------------------------+

    IF __CONNECTED(cid) THEN '            is controller connected?
        WHILE _DEVICEINPUT(cid): WEND '   yes, get latest controller values
        __CONTROLLER_AXIS = _AXIS(Axis) ' return controller axis value
    END IF

END FUNCTION
' ______________________________________________________________________________________________________________________________________________
'/                                                                                                                                              \
FUNCTION __CONTROLLER_NAME$ (cid AS INTEGER) '                                                                               __CONTROLLER_NAME$ |
    ' __________________________________________________________________________________________________________________________________________|____
    '/                                                                                                                                               \
    '| Returns the descriptive name of the controller.                                                                                               |
    '|                                                                                                                                               |
    '| PRINT __CONTROLLER_NAME$(__JOYPAD1CID)                                                                                                        |
    '|                                                                                                                                               |
    '| cid - the id of the controller                                                                                                                |
    '\_______________________________________________________________________________________________________________________________________________/

    SHARED CL_CONTROLLER() AS TYPE__CONTROLLER ' need access to controller array

    '+------------------+
    '| Check for errors |
    '+------------------+

    __CURRENT_ROUTINE = "__CONTROLLER_NAME$"
    IF cid < 1 OR cid > 6 THEN __ERROR "Invalid controller id."
    IF CL_CONTROLLER(cid).Found = 0 THEN __ERROR "The specified controller does not exist"
    __PREVIOUS_ROUTINE = __CURRENT_ROUTINE

    '+---------------------------+
    '| Return name of controller |
    '+---------------------------+

    __CONTROLLER_NAME$ = CL_CONTROLLER(cid).Name ' return the name of the controller

END FUNCTION
' ______________________________________________________________________________________________________________________________________________
'/                                                                                                                                              \
FUNCTION __BUTTON_DOWN (Handle AS INTEGER) '                                                                                      __BUTTON_DOWN |
    ' __________________________________________________________________________________________________________________________________________|____
    '/                                                                                                                                               \
    '| Returns -1 (TRUE) if a button is pressed, 0 (FALSE) otherwise.                                                                                |
    '|                                                                                                                                               |
    '| State = __BUTTON_DOWN(UPButton)                                                                                                               |
    '|                                                                                                                                               |
    '| Handle - the handle of the button to check                                                                                                    |
    '\_______________________________________________________________________________________________________________________________________________/

    SHARED CL_BUTTON() AS TYPE__BUTTON '   need access to button array
    SHARED CL_SETTINGS AS TYPE__SETTINGS ' need access to library settings
    DIM Slot AS INTEGER '                  button/axis slot counter
    DIM cid AS INTEGER '                   controller id
    DIM Button AS INTEGER '                controller button
    DIM Axis AS INTEGER '                  controller axis
    DIM AxisValue AS SINGLE '              controller current axis value
    DIM Down AS INTEGER '                  button is down (t/f)

    '+------------------+
    '| Check for errors |
    '+------------------+

    __CURRENT_ROUTINE = "__BUTTON_DOWN"
    IF Handle < 1 OR Handle > UBOUND(CL_BUTTON) THEN __ERROR "The specified button does not exist."
    __PREVIOUS_ROUTINE = __CURRENT_ROUTINE

    '+-------------------------+
    '| Report status of button |
    '+-------------------------+

    Down = 0 '                                                         assume no button/axis pressed
    Slot = 0 '                                                         reset slot counter
    DO '                                                               begin button/axis down search
        Slot = Slot + 1 '                                              increment slot counter

        '+----------------------------------------------------------+
        '| Get the controller's id, button, and axis from each slot |
        '+----------------------------------------------------------+

        SELECT CASE Slot '                                             which button/axis slot?
            CASE 1 '                                                   slot 1
                cid = CL_BUTTON(Handle).Slot1.cid '                    get controller's id from slot 1
                Button = CL_BUTTON(Handle).Slot1.Button '              get controller's button from slot 1
                Axis = CL_BUTTON(Handle).Slot1.Axis '                  get controller's axis from slot 1
            CASE 2 '                                                   slot 2
                cid = CL_BUTTON(Handle).Slot2.cid
                Button = CL_BUTTON(Handle).Slot2.Button
                Axis = CL_BUTTON(Handle).Slot2.Axis
            CASE 3 '                                                   slot 3
                cid = CL_BUTTON(Handle).Slot3.cid
                Button = CL_BUTTON(Handle).Slot3.Button
                Axis = CL_BUTTON(Handle).Slot3.Axis
            CASE 4 '                                                   slot 4
                cid = CL_BUTTON(Handle).Slot4.cid
                Button = CL_BUTTON(Handle).Slot4.Button
                Axis = CL_BUTTON(Handle).Slot4.Axis
        END SELECT
        IF cid THEN '                                                  is there a controller id?

            '+---------------------------------------+
            '| A controller id was found in the slot |
            '+---------------------------------------+

            WHILE _DEVICEINPUT(cid): WEND '                            yes, get controller's latest values
            IF Button THEN '                                           does a controller button need checked?

                '+--------------------------------------------+
                '| This slot contained a button to be checked |
                '+--------------------------------------------+

                IF _BUTTON(Button) THEN Down = -1 '                    yes, get state of controller button
            ELSEIF Axis THEN '                                         no, does a controller axis need checked?

                '+-------------------------------------------+
                '| This slot contained an axis to be checked |
                '+-------------------------------------------+

                AxisValue = _AXIS(ABS(Axis)) '                         yes, get the current controller axis value
                IF ABS(AxisValue) >= CL_SETTINGS.Threshold THEN '      is axis deflected at least to sensitivity setting?
                    IF SGN(AxisValue) = SGN(Axis) THEN Down = -1 '     yes, get state of axis
                END IF
            END IF
        END IF
    LOOP UNTIL (Slot = 4) OR Down '                                    leave when all four slots checked or a button is down
    __BUTTON_DOWN = Down '                                             return state of button

END FUNCTION
' ______________________________________________________________________________________________________________________________________________
'/                                                                                                                                              \
SUB __AUTOASSIGN_BUTTON (Handle AS INTEGER) '                                                                               __AUTOASSIGN_BUTTON |
    ' __________________________________________________________________________________________________________________________________________|____
    '/                                                                                                                                               \
    '| Automatically assigns a button handle with up to a combination of four controller buttons or axis deflections. Simply call this subroutine up |
    '| to four times to associate each axis or button with the handle. This allows axis deflections to be treated as button presses. Good for top    |
    '| hats and Nintendo style "plus" directionals.                                                                                                  |
    '|                                                                                                                                               |
    '| __AUTOASSIGN_BUTTON UPButton ' player chooses the UP ARROW key for instance and that gets saved in SLOT 1                                     |
    '| __AUTOASSIGN_BUTTON UPButton ' player chooses the W key for instance and that gets saved in SLOT 2                                            |
    '| __AUTOASSIGN_BUTTON UPButton ' player chooses the UP direction on a "plus" pad for instance and that gets saved in SLOT 3                     |
    '| '                              the player now has three different methods of inputting a directional UP movement                              |
    '|                                                                                                                                               |
    '| Handle - button handle                                                                                                                        |
    '|                                                                                                                                               |
    '| The result of the scan is sent to either __ASSIGN_AXIS or __ASSIGN_BUTTON. See the documentation for these two subrotuines for more           |
    '| information on how the resulting values are stored and used.                                                                                  |
    '|                                                                                                                                               |
    '| NOTE: Once all four button slots are filled any attempt to associate a handle with more axis or buttons is ignored.                           |
    '|       Mouse axes are ignored during auto assign. Use __ASSIGN_AXIS if you wish to assign a mouse axis as a button (not recommended).          |
    '\_______________________________________________________________________________________________________________________________________________/

    SHARED CL_CONTROLLER() AS TYPE__CONTROLLER ' need access to controller array
    SHARED CL_SETTINGS AS TYPE__SETTINGS '       need access to library settings
    DIM cid AS INTEGER '                         controller id
    DIM Number AS INTEGER '                      axis/button number (or keyboard scan code) counter
    DIM Button AS INTEGER '                      button number (or keyboard scan code) that was pressed
    DIM Axis AS INTEGER '                        axis that was deflected
    DIM AxisValue AS SINGLE '                    value of axis that was deflected

    DO '                                                                  begin controller interaction search

        '+---------------------------------------------+
        '| Wait for a controller to be interacted with |
        '+---------------------------------------------+

        _LIMIT 60 '                                                       don't hog the CPU while waiting
        cid = _DEVICEINPUT '                                              check for a controller interaction
        IF cid THEN '                                                     was a controller interacted with?

            '+----------------------------------+
            '| A controller was interacted with |
            '+----------------------------------+

            WHILE _DEVICEINPUT(cid): WEND

            IF CL_CONTROLLER(cid).Buttons <> 0 THEN '                     yes, does this controler have buttons?

                '+---------------------------------------+
                '| This controller has buttons available |
                '+---------------------------------------+

                Number = 0 '                                              yes, reset button number counter
                Button = 0 '                                              reset button press number
                DO '                                                      begin button search

                    '+--------------------------------------+
                    '| Get button (if any) that was pressed |
                    '+--------------------------------------+

                    Number = Number + 1 '                                 increment button number counter
                    IF _BUTTON(Number) THEN Button = Number '             record this button number if it was pressed
                LOOP UNTIL Number = _LASTBUTTON(cid) OR Button '          leave when all buttons checked or a button weas pressed
            END IF

            '+--------------------------------------------------+
            '| A check for axis deflection will now be done.    |
            '| The mouse is purposely excluded from this check. |
            '+--------------------------------------------------+

            IF cid <> __MOUSECID THEN '                                   is this the mouse controller?
                IF CL_CONTROLLER(cid).Axis <> 0 THEN '                    no, does this controller have axis?

                    '+------------------------------------+
                    '| This controller has axis available |
                    '+------------------------------------+

                    Number = 0 '                                          yes, reset axis number counter
                    Axis = 0 '                                            reset axis deflection number
                    DO '                                                  begin axis search

                        '+--------------------------------------------------------------------------+
                        '| Get axis (if any) that was deflected at least 50% (or threshold setting) |
                        '+--------------------------------------------------------------------------+

                        Number = Number + 1 '                             increment axis number counter
                        AxisValue = _AXIS(Number) '                       get current value of axis
                        IF ABS(AxisValue) >= CL_SETTINGS.Threshold THEN ' was axis delfected at least to sensitivity setting?

                            '+----------------------------------------------------------------------------------------------------------+
                            '| The axis number is recorded as a negative value if the deflection was in a negative direction.           |
                            '| Likewise, the axis number is recorded as a positive value if the deflection was in a positive direction. |
                            '| This allows one axis to be recorded as two separate button actions (UP/DOWN or LEFT/RIGHT).              |
                            '+----------------------------------------------------------------------------------------------------------+

                            Axis = Number * SGN(AxisValue) '              yes, record axis with sign (+/-) of deflection
                        END IF
                    LOOP UNTIL Number = _LASTAXIS(cid) OR Axis '          leave when all axis checked or an axis was deflected
                END IF
            END IF
        END IF
    LOOP UNTIL cid <> 0 AND (Button <> 0 OR Axis <> 0) '                  leave when a controller interacted with and interaction was with a button or axis
    IF cid = __KEYBOARDCID THEN _KEYCLEAR '                               clear all keyboard buffers if the keyboard was interacted with
    IF Button THEN __ASSIGN_BUTTON Handle, cid, Button '                  assign the button to the handle
    IF Axis THEN __ASSIGN_AXIS Handle, cid, Axis '                        assign the axis to the handle

END SUB
' ______________________________________________________________________________________________________________________________________________
'/                                                                                                                                              \
SUB __ASSIGN_AXIS (Handle AS INTEGER, cid AS INTEGER, Axis AS INTEGER) '                                                          __ASSIGN_AXIS |
    ' __________________________________________________________________________________________________________________________________________|____
    '/                                                                                                                                               \
    '| Associates a handle with up to four axis from the mouse or joypads. Simply call this subroutine up to four times to associate a new axis to   |
    '| the handle. This allows axis deflections to be treated as button presses. Good for top hats and Nintendo style "plus" directionals.           |
    '|                                                                                                                                               |
    '| __ASSIGN_AXIS UPButton, __JOYPAD1INPUT, -1 '   joystick/gamepad axis 1 UP                                                                     |
    '| __ASSIGN_AXIS DOWNButton, __JOYPAD1INPUT, 1 '  joystick/gamepad axis 1 DOWN                                                                   |
    '| __ASSIGN_AXIS LEFTButton, __JOYPAD1INPUT, -2 ' joystick/gamepad axis 2 LEFT                                                                   |
    '| __ASSIGN_AXIS RIGHTButton, __JOYPAD1INPUT, 2 ' joystick/gamepad axis 2 RIGHT                                                                  |
    '|                                                                                                                                               |
    '| Handle - button handle                                                                                                                        |
    '| cid    - controller id          (1-keyboard, 2-mouse, 3-joypad, etc..)                                                                        |
    '| Axis   - controller axis number ( 1 to _LASTAXIS(id) )                                                                                        |
    '|          the sign (+/-) of Axis determines which deflection direction will be used as a button press.                                         |
    '|          Negative typically means UP or LEFT and positive typically means DOWN or RIGHT.                                                      |
    '|                                                                                                                                               |
    '| NOTE: Once all four button slots are filled any attempt to associate a handle with more axis is ignored.                                      |
    '\_______________________________________________________________________________________________________________________________________________/

    SHARED CL_CONTROLLER() AS TYPE__CONTROLLER ' need access to controller array
    SHARED CL_BUTTON() AS TYPE__BUTTON '         need access to button array
    SHARED CL_SETTINGS AS TYPE__SETTINGS '       need access to library settings
    SHARED CL_KEYNAME() AS STRING '              need access to _BUTTON keyboard key names
    DIM Assigning AS TYPE__SLOT '                check slot UDT
    DIM AssignedHandle AS INTEGER '              previously assigned button handle
    DIM AssignedSlot AS INTEGER '                previously assigned button handle slot
    DIM Aname AS STRING '                        descriptive axis name
    DIM Jname AS STRING '                        joystick/game pad name

    '+------------------+
    '| Check for errors |
    '+------------------+

    __CURRENT_ROUTINE = "__BUTTON_AXIS_ASSIGN"
    IF CL_CONTROLLER(cid).Found = 0 THEN __ERROR "The specified controller does not exist."
    IF ABS(Axis) < 1 OR ABS(Axis) > _LASTAXIS(cid) THEN __ERROR "The axis specified does not exist on controller."
    __PREVIOUS_ROUTINE = __CURRENT_ROUTINE

    '+-----------------------------------+
    '| Check if axis is already assigned |
    '+-----------------------------------+

    Assigning.cid = cid '                                                      set up check slot UDT
    Assigning.Button = 0
    Assigning.Axis = Axis

    IF IUO__ALREADY_ASSIGNED(Assigning, AssignedHandle, AssignedSlot) THEN '   is this axis button already assigned?

        '+-------------------------------------+
        '| This axis has already been assigned |
        '+-------------------------------------+

        IF Handle = AssignedHandle THEN '                                      is this axis already assigned to this handle?

            '+---------------------------------------------+
            '| This axis is aleady assigned to this handle |
            '+---------------------------------------------+

            EXIT SUB '                                                         yes, leave subroutine so duplicate is not made
        ELSE '                                                                 no, this is a new valid assignment
            IF CL_SETTINGS.Reassign THEN '                                     yes, is reassigning allowed?

                '+-----------------------------------+
                '| Reassigning of buttons is allowed |
                '+-----------------------------------+

                IF Handle = 0 OR CL_BUTTON(Handle).Slot1.cid = 0_
                              OR CL_BUTTON(Handle).Slot2.cid = 0_
                              OR CL_BUTTON(Handle).Slot3.cid = 0_
                              OR CL_BUTTON(Handle).Slot4.cid = 0 THEN '        yes, is there a slot available?

                    '+---------------------------------------------------------------+
                    '| This is either a new user assigned button or an existing user |
                    '| assigned button with a slot availabe so remove old assignment |
                    '+---------------------------------------------------------------+

                    __REMOVE_BUTTON AssignedHandle, AssignedSlot '             yes, remove previous button assignment
                END IF
            ELSE '                                                             no, reassigning not allowed

                '+---------------------------------------+
                '| Reassigning of buttons is not allowed |
                '+---------------------------------------+

                EXIT SUB '                                                     leave subroutine
            END IF
        END IF
    END IF

    '+----------------------------------------+
    '| Create a descriptive name for the axis |
    '+----------------------------------------+

    Jname = "" '                                                               clear joystick name
    SELECT CASE cid '                                                          which controller?
        CASE __MOUSECID '                                                      mouse
            IF ABS(Axis) = 1 THEN '                                            is this axis 1?
                IF SGN(Axis) = -1 THEN '                                       yes, up (negative)?
                    Aname = "Mouse Up" '                                       yes, create name
                ELSE '                                                         no, down (positive)
                    Aname = "Mouse Down" '                                     create name
                END IF
            ELSE '                                                             no, this must be axis 2
                IF SGN(Axis) = -1 THEN '                                       left (negative)?
                    Aname = "Mouse Left" '                                     yes, create name
                ELSE '                                                         no, right (positive)
                    Aname = "Mouse Right" '                                    create name
                END IF
            END IF
        CASE __JOYPAD1CID: Jname = "J1" '                                      joystick/game pad 1
        CASE __JOYPAD2CID: Jname = "J2" '                                      joystick/game pad 2
        CASE __JOYPAD3CID: Jname = "J3" '                                      joystick/game pad 3
        CASE __JOYPAD4CID: Jname = "J4" '                                      joystick/game pad 4
        CASE __JOYPAD5CID: Jname = "J5" '                                      joystick/game pad 5
        CASE __JOYPAD6CID: Jname = "J6" '                                      joystick/game pad 6
    END SELECT
    IF Jname <> "" THEN '                                                      was a joystick name given?

        '+-----------------------------------------+
        '| The axis belongs to a joystick/game pad |
        '+-----------------------------------------+

        SELECT CASE ABS(Axis) '                                                        yes, which axis?
            CASE 1: IF SGN(Axis) = -1 THEN Aname = "A1 Left" ELSE Aname = "A1 Right" ' axis 1, left or right
            CASE 2: IF SGN(Axis) = -1 THEN Aname = "A2 Up" ELSE Aname = "A2 Down" '    axis 2, up or down
            CASE 3: IF SGN(Axis) = -1 THEN Aname = "A3 Left" ELSE Aname = "A3 Right" ' axis 3, left or right
            CASE 4: IF SGN(Axis) = -1 THEN Aname = "A4 Up" ELSE Aname = "A4 Down" '    axis 4, up or down
        END SELECT
        Aname = Jname + Aname '                                                        complete joystick name
    END IF

    '+-------------------------+
    '| Save button information |
    '+-------------------------+

    Assigning.cid = cid '                                                      set up button to save
    Assigning.Button = 0
    Assigning.Axis = Axis
    Assigning.Name = Aname
    Assigning.Cname = __CONTROLLER_NAME$(cid)
    IF CL_BUTTON(Handle).Slot4.cid THEN '                                      4 buttons/axis already assigned?

        '+--------------------------------------------------------------------------+
        '| There are no more available slots to assign another axis to this handle. |
        '+--------------------------------------------------------------------------+

        EXIT SUB '                                                             yes, leave subroutine, no more room
    ELSEIF CL_BUTTON(Handle).Slot3.cid THEN '                                  no, 3 buttons/axis already assigned?
        CL_BUTTON(Handle).Slot4 = Assigning
    ELSEIF CL_BUTTON(Handle).Slot2.cid THEN '                                  no, 2 buttons/axis already assigned?
        CL_BUTTON(Handle).Slot3 = Assigning
    ELSEIF CL_BUTTON(Handle).Slot1.cid THEN '                                  no, 1 button/axis already assigned?
        CL_BUTTON(Handle).Slot2 = Assigning
    ELSE '                                                                     no buttons/axis have been assigned yet
        CL_BUTTON(Handle).Slot1 = Assigning
    END IF

END SUB
' ______________________________________________________________________________________________________________________________________________
'/                                                                                                                                              \
SUB __ASSIGN_BUTTON (Handle AS INTEGER, cid AS INTEGER, Button AS INTEGER) '                                                    __ASSIGN_BUTTON |
    ' __________________________________________________________________________________________________________________________________________|____
    '/                                                                                                                                               \
    '| Associates a handle with up to four buttons from the keyboard, mouse, or joypads. Simply call this subroutine up to four times to associate a |
    '| new button to the handle.                                                                                                                     |
    '|                                                                                                                                               |
    '| __ASSIGN_BUTTON FireButton, __KEYBOARDINPUT, CLKEY_SPACEBAR ' keyboard space bar          (in first slot)                                     |
    '| __ASSIGN_BUTTON FireButton, __JOYPAD1INPUT, 1 '               joystick trigger (button 1) (in second slot)                                    |
    '|                                                                                                                                               |
    '| Handle - button handle                                                                                                                        |
    '| cid    - controller id            (1-keyboard, 2-mouse, 3-joypad, etc..)                                                                      |
    '| Button - controller button number ( 1 to _LASTBUTTON(id) )                                                                                    |
    '|                                                                                                                                               |
    '| NOTE: Once all four button slots are filled any attempt to associate a handle with more buttons is ignored.                                   |
    '\_______________________________________________________________________________________________________________________________________________/

    SHARED CL_CONTROLLER() AS TYPE__CONTROLLER ' need access to controller array
    SHARED CL_BUTTON() AS TYPE__BUTTON '         need access to button array
    SHARED CL_SETTINGS AS TYPE__SETTINGS '       need access to library settings
    SHARED CL_KEYNAME() AS STRING '              need access to _BUTTON keyboard key names
    DIM Assigning AS TYPE__SLOT '                check slot UDT
    DIM AssignedHandle AS INTEGER '              previously assigned button handle
    DIM AssignedSlot AS INTEGER '                previously assigned button handle slot
    DIM Bname AS STRING '                        descriptive button name

    '+------------------+
    '| Check for errors |
    '+------------------+

    __CURRENT_ROUTINE = "__BUTTON_ASSIGN"
    IF CL_CONTROLLER(cid).Found = 0 THEN __ERROR "The specified controller does not exist."
    IF Button < 1 OR Button > _LASTBUTTON(cid) THEN __ERROR "The button specified does not exist on controller."
    __PREVIOUS_ROUTINE = __CURRENT_ROUTINE

    '+-------------------------------------+
    '| Check if button is already assigned |
    '+-------------------------------------+

    Assigning.cid = cid '                                                      set up check slot UDT
    Assigning.Button = Button
    Assigning.Axis = 0

    IF IUO__ALREADY_ASSIGNED(Assigning, AssignedHandle, AssignedSlot) THEN '   is this button already assigned?

        '+---------------------------------------+
        '| This button has already been assigned |
        '+---------------------------------------+

        IF Handle = AssignedHandle THEN '                                      is this button already assigned to this handle?

            '+-----------------------------------------------+
            '| This button is aleady assigned to this handle |
            '+-----------------------------------------------+

            EXIT SUB '                                                         yes, leave subroutine so duplicate is not made
        ELSE '                                                                 no, this is a new valid assignment
            IF CL_SETTINGS.Reassign THEN '                                     is reassigning allowed?

                '+-----------------------------------+
                '| Reassigning of buttons is allowed |
                '+-----------------------------------+

                IF Handle = 0 OR CL_BUTTON(Handle).Slot1.cid = 0_
                              OR CL_BUTTON(Handle).Slot2.cid = 0_
                              OR CL_BUTTON(Handle).Slot3.cid = 0_
                              OR CL_BUTTON(Handle).Slot4.cid = 0 THEN '        yes, is there a slot available?

                    '+---------------------------------------------------------------+
                    '| This is either a new user assigned button or an existing user |
                    '| assigned button with a slot availabe so remove old assignment |
                    '+---------------------------------------------------------------+

                    __REMOVE_BUTTON AssignedHandle, AssignedSlot '             yes, remove previous button assignment
                END IF
            ELSE '                                                             no, reassigning not allowed

                '+---------------------------------------+
                '| Reassigning of buttons is not allowed |
                '+---------------------------------------+

                EXIT SUB '                                                     leave subroutine
            END IF
        END IF
    END IF

    '+------------------------------------------+
    '| Create a descriptive name for the button |
    '+------------------------------------------+

    SELECT CASE cid '                                                          which controller?
        CASE __KEYBOARDCID '                                                   keyboard
            Bname = CL_KEYNAME(Button) + " Key" '                              create descriptive keyboard key name
        CASE __MOUSECID '                                                      mouse
            IF Button = 1 THEN '                                               left button?
                Bname = "Left Mouse" '                                         yes, create name
            ELSEIF Button = 2 THEN '                                           no, middle button?
                Bname = "Center Mouse" '                                       yes, create name
            ELSE '                                                             no, must be right button
                Bname = "Right Mouse" '                                        create name
            END IF
        CASE __JOYPAD1CID: Bname = "J1B" + _TRIM$(STR$(Button)) '              joystick/game pad 1, create name
        CASE __JOYPAD2CID: Bname = "J2B" + _TRIM$(STR$(Button)) '              joystick/game pad 2, create name
        CASE __JOYPAD3CID: Bname = "J3B" + _TRIM$(STR$(Button)) '              joystick/game pad 3, create name
        CASE __JOYPAD4CID: Bname = "J4B" + _TRIM$(STR$(Button)) '              joystick/game pad 4, create name
        CASE __JOYPAD5CID: Bname = "J5B" + _TRIM$(STR$(Button)) '              joystick/game pad 5, create name
        CASE __JOYPAD6CID: Bname = "J6B" + _TRIM$(STR$(Button)) '              joystick/game pad 6, create name
    END SELECT

    '+-------------------------+
    '| Save button information |
    '+-------------------------+

    Assigning.cid = cid '                                                      set up button to save
    Assigning.Button = Button
    Assigning.Axis = 0
    Assigning.Name = Bname
    Assigning.Cname = __CONTROLLER_NAME$(cid)
    IF CL_BUTTON(Handle).Slot4.cid THEN '                                      4 buttons/axis already assigned?

        '+----------------------------------------------------------------------------+
        '| There are no more available slots to assign another button to this handle. |
        '+----------------------------------------------------------------------------+

        EXIT SUB '                                                             yes, leave subroutine, no more room
    ELSEIF CL_BUTTON(Handle).Slot3.cid THEN '                                  no, 3 buttons/axis already assigned?
        CL_BUTTON(Handle).Slot4 = Assigning
    ELSEIF CL_BUTTON(Handle).Slot2.cid THEN '                                  no, 2 buttons/axis already assigned?
        CL_BUTTON(Handle).Slot3 = Assigning
    ELSEIF CL_BUTTON(Handle).Slot1.cid THEN '                                  no, 1 button/axis already assigned?
        CL_BUTTON(Handle).Slot2 = Assigning
    ELSE '                                                                     no buttons/axis have been assigned yet
        CL_BUTTON(Handle).Slot1 = Assigning
    END IF

END SUB
' ______________________________________________________________________________________________________________________________________________
'/                                                                                                                                              \
SUB __IDENTIFY_CONTROLLERS () '                                                                                          __IDENTIFY_CONTROLLERS |
    ' __________________________________________________________________________________________________________________________________________|____
    '/                                                                                                                                               \
    '| Finds all controllers (keyboard, mouse, gamepad, joystick) connected to the computer and polls them for information.                          |
    '|                                                                                                                                               |
    '| __IDENTIFY_CONTROLLERS                                                                                                                        |
    '|                                                                                                                                               |
    '| The following SHARED variables will be set with the controller's device id:                                                                   |
    '|   __KEYBOARDCID - usually 1 if found, 0 otherwise                                                                                             |
    '|   __MOUSECID    - usually 2 if found, 0 otherwise                                                                                             |
    '|   __JOYPAD1CID  - usually 3 if found (typical: 1st game pad, 1st joystick, 1st flight yoke, 1st steering wheel)                               |
    '|   __JOYPAD2CID  - usually 4 if found (typical: 2nd game pad, 2nd joystick, 2nd flight yoke, 1st rudder pedals, 1st gas/brake pedals)          |
    '|   __JOYPAD3CID  - usually 5 if found (typical: 3rd game pad, 3rd joystick, 3rd flight yoke, 2nd rudder pedals, 2nd steering wheel)            |
    '|   __JOYPAD4CID  - usually 6 if found (typical: 4th game pad, 4th joystick, 4th flight yoke, 2nd/3rd rudder pedals, 2nd/3rd gas/brake pedals)  |
    '|                                                                                                                                               |
    '| NOTE: The library will store the information for up to 6 controllers. This is typically 1 keyboard, 1 mouse, and up to four game pads and/or  |
    '|       joysticks. The "typical:" devices listed above are what I experienced when plugging these devices into my system. Your configuration    |
    '|       may vary wildly. If no keyboard or mouse is present it's possible to have up to six joysticks/game pads detected and stored.            |
    '\_______________________________________________________________________________________________________________________________________________/

    SHARED CL_CONTROLLER() AS TYPE__CONTROLLER ' need access to controller array
    SHARED CL_SETTINGS AS TYPE__SETTINGS '       need access to library settings
    DIM Devices AS INTEGER '                     number of controller devices connected to computer
    DIM Device AS STRING '                       description and properties of controller device
    DIM cid AS INTEGER '                         controller id loop counter
    DIM Start AS INTEGER '                       beginning position of device name
    DIM Finish AS INTEGER '                      ending position of device name

    __KEYBOARDCID = 0 '                                                         reset input controller identifiers
    __MOUSECID = 0
    __JOYPAD1CID = 0
    __JOYPAD2CID = 0
    __JOYPAD3CID = 0
    __JOYPAD4CID = 0
    __JOYPAD5CID = 0
    __JOYPAD6CID = 0
    Devices = _DEVICES '                                                        get number of controllers connected to system
    CL_SETTINGS.FoundDevices = Devices '                                        record number of devices initially found
    cid = 0 '                                                                   reset controller counter
    DO '                                                                        begin controller identification loop
        cid = cid + 1 '                                                         increment controller counter
        IF cid <= Devices THEN '                                                was a controller found?
            Device = _DEVICE$(cid) '                                            yes, get controller properties
            CL_CONTROLLER(cid).Found = -1 '                                     mark this controller as found
            IF INSTR(Device, "[KEYBOARD]") THEN '                               is this a keyboard?
                CL_CONTROLLER(cid).Name = "KEYBOARD" '                          set a default descriptive name
                __KEYBOARDCID = cid '                                           set keyboard's input controller id
            END IF
            IF INSTR(Device, "[MOUSE]") THEN '                                  is this a mouse?
                CL_CONTROLLER(cid).Name = "MOUSE" '                             set a default descriptive name
                __MOUSECID = cid '                                              set mouse' input controller id
            END IF
            IF INSTR(Device, "[DISCONNECTED]") THEN '                           is this controller disconnected?
                CL_CONTROLLER(cid).Connected = 0 '                              yes, mark controller as disconnected
            ELSE '                                                              no, controller is connected
                CL_CONTROLLER(cid).Connected = -1 '                             mark controller as connected
            END IF
            IF INSTR(Device, "[CONTROLLER]") THEN '                             is this a game pad or joystick?

                '+---------------------------------------------------------+
                '| A generic name will be given to the joystick/game pad   |
                '| in the event the controller may not have a [NAME] field |
                '| Note: controllers 5 and/or 6 will only exist if the     |
                '|       keyboard and/or mouse are not connected           |
                '+---------------------------------------------------------+

                IF __JOYPAD5CID THEN '                                          has joypad5's id already been set?
                    __JOYPAD6CID = cid '                                        yes, set joypad6's input controller id
                    CL_CONTROLLER(cid).Name = "JOYPAD6" '                       set a default descriptive name
                ELSEIF __JOYPAD4CID THEN '                                      has joypad4's id already been set?
                    __JOYPAD5CID = cid '                                        yes, set joypad5's input controller id
                    CL_CONTROLLER(cid).Name = "JOYPAD5" '                       set a default descriptive name
                ELSEIF __JOYPAD3CID THEN '                                      has joypad3's id already been set?
                    __JOYPAD4CID = cid '                                        yes, set joypad4's input controller id
                    CL_CONTROLLER(cid).Name = "JOYPAD4" '                       set a default descriptive name
                ELSEIF __JOYPAD2CID THEN '                                      no, has joypad2's id already been set?
                    __JOYPAD3CID = cid '                                        yes, set joypad3's input controller id
                    CL_CONTROLLER(cid).Name = "JOYPAD3" '                       set a default descriptive name
                ELSEIF __JOYPAD1CID THEN '                                      no, has joypad1's id already been set?
                    __JOYPAD2CID = cid '                                        yes, set joypad2's input controller id
                    CL_CONTROLLER(cid).Name = "JOYPAD2" '                       set a default descriptive name
                ELSE '                                                          no joypad ids set yet
                    __JOYPAD1CID = cid '                                        set joypad1's input controller id
                    CL_CONTROLLER(cid).Name = "JOYPAD1" '                       set a default descriptive name
                END IF
            END IF
            IF INSTR(Device, "[BUTTON]") THEN '                                 does this controller have buttons?
                CL_CONTROLLER(cid).Buttons = _LASTBUTTON(cid) '                 yes, record number of buttons controller has
            ELSE '                                                              no
                CL_CONTROLLER(cid).Buttons = 0 '                                record no buttons
            END IF
            IF INSTR(Device, "[AXIS]") THEN '                                   does this controller have axis inputs?
                CL_CONTROLLER(cid).Axis = _LASTAXIS(cid) '                      yes, record number of axis controller has
            ELSE '                                                              no
                CL_CONTROLLER(cid).Axis = 0 '                                   record no axis
            END IF
            IF INSTR(Device, "[WHEEL]") THEN '                                  does this controller have wheels?
                CL_CONTROLLER(cid).Wheels = _LASTWHEEL(cid) '                   yes, record number of wheels controller has
            ELSE '                                                              no
                CL_CONTROLLER(cid).Wheels = 0 '                                 record no wheels
            END IF
            IF INSTR(Device, "[NAME]") THEN '                                   is there a more descriptive name for this controller?

                '+-------------------------------------------------------------+
                '| Replace the generic name given with the controller's [NAME] |
                '+-------------------------------------------------------------+

                Start = INSTR(Device, "[NAME]") + 7 '                           yes, get the start position of the name
                Finish = INSTR(Start, Device, "]") '                            get the end position of the name
                CL_CONTROLLER(cid).Name = MID$(Device, Start, Finish - Start) ' extract and only use the descriptive name of the controller
            END IF
        ELSE '                                                                  no controller here
            CL_CONTROLLER(cid) = CL_CONTROLLER(0) '                             reset all controller settings
        END IF
    LOOP UNTIL cid = Devices OR cid = 6 '                                       leave when all controllers polled

END SUB
' ______________________________________________________________________________________________________________________________________________
'/                                                                                                                                              \
SUB __INITIALIZE_CONTROLLERS () '                                                                                      __INITIALIZE_CONTROLLERS |
    ' __________________________________________________________________________________________________________________________________________|____
    '/                                                                                                                                               \
    '| Identify number and type of controllers and set initial variable values                                                                       |
    '|                                                                                                                                               |
    '| __INITIALIZE_CONTROLLERS                                                                                                                      |
    '\_______________________________________________________________________________________________________________________________________________/

    SHARED CL_KEYNAME() AS STRING '              need access to _BUTTON keyboard key names
    SHARED CL_BUTTON() AS TYPE__BUTTON '         need access to user assigned buttons
    SHARED CL_CONTROLLER() AS TYPE__CONTROLLER ' need access to controller array
    REDIM CL_BUTTON(1) AS TYPE__BUTTON '         clear user assigned buttons
    DIM k AS INTEGER '                           key counter

    __KEYBOARDCID = 0 '                      these will contain the device ids (_DEVICES)
    __MOUSECID = 0
    __JOYPAD1CID = 0
    __JOYPAD2CID = 0
    __JOYPAD3CID = 0
    __JOYPAD4CID = 0
    __JOYPAD5CID = 0
    __JOYPAD6CID = 0
    FOR k = 0 TO 6 '                         clear controller array
        CL_CONTROLLER(k).Found = 0
        CL_CONTROLLER(k).Name = ""
        CL_CONTROLLER(k).Buttons = 0
        CL_CONTROLLER(k).Axis = 0
        CL_CONTROLLER(k).Wheels = 0
    NEXT k
    __IDENTIFY_CONTROLLERS '                 identify controllers attached to system
    __SET_AXIS_THRESHOLD .5 '                set sensitivity of axis user defined button detection
    __BUTTON_REASSIGN_ALLOWED '              allow user defined button reassignments
    FOR k = 1 TO 350 '                       read in names of keyboard controller keys
        CL_KEYNAME(k) = "" '                 clear key name
        IF k < 90 THEN READ CL_KEYNAME(k) '  first 89 names are in data statements
        IF k = 285 THEN READ CL_KEYNAME(k) ' 285, 90 through 284 are skipped
        IF k = 286 THEN READ CL_KEYNAME(k) ' 286
        IF k = 310 THEN READ CL_KEYNAME(k) ' 310, 287 through 309 are skipped
        IF k > 325 THEN READ CL_KEYNAME(k) ' 326 through 350 are in data statements
    NEXT k

    'Keyboard _BUTTON   1 - 20
    DATA "","Escape","1","2","3","4","5","6","7","8","9","0","Minus","Equals","Backspace","Tab","Q","W","E","R"
    'Keyboard _BUTTON  21 - 40
    DATA "T","Y","U","I","O","P","Left Bracket","Right Bracket","Enter","Left CTRL","A","S","D","F","G","H","J","K","L","Semicolon"
    'Keyboard _BUTTON  41 - 50
    DATA "Quote","","Left Shift","Back Slash","Z","X","C","V","B","N"
    'Keyboard _BUTTON  51 - 60
    DATA "M","Comma","Period","Fore Slash","Right Shift","Numpad Multiply","","Space Bar","Caps Lock","F1"
    'Keyboard _BUTTON  61 - 70
    DATA "F2","F3","F4","F5","F6","F7","F8","F9","","Pause"
    'Keyboard _BUTTON  71 - 80
    DATA "Scroll Lock","Numpad 7","Numpad 8","Numpad 9","Numpad Minus","Numpad 4","Numpad 5","Numpad 6","Numpad Plus","Numpad 1"
    'Keyboard _BUTTON  81 - 89
    DATA "Numpad 2","Numpad 3","Numpad 0","Numpad Period","","","","F11","F12"
    'Keyboard _BUTTON 285 - 286
    DATA "Numpad Enter","Right CTRL"
    'Keyboard _BUTTON 310
    DATA "Numpad Divide"
    'Keyboard _BUTTON 326 - 340
    DATA "Number Lock","","Home","UP Arrow","Page UP","","LEFT Arrow","","RIGHT Arrow","","End","DOWN Arrow","Page Down","Insert","Delete"
    'Keyboard _BUTTON 341 - 350
    DATA "","","","","","","","Left Win","Right Win","Menu"

END SUB
' ______________________________________________________________________________________________________________________________________________
'/                                                                                                                                              \
SUB __ERROR (Message AS STRING) '                                                                                                       __ERROR |
    ' __________________________________________________________________________________________________________________________________________|____
    '/                                                                                                                                               \
    '| Set screen to text mode and display error information passed in.                                                                              |
    '|                                                                                                                                               |
    '| __ERROR "Message"                                                                                                                             |
    '|                                                                                                                                               |
    '| NOTE: Fatal error trap - halts program execution.                                                                                             |
    '\_______________________________________________________________________________________________________________________________________________/

    _FULLSCREEN _OFF '                                                    turn off full screen if active
    SCREEN 0, 0, 0, 0 '                                                   set screen to pure text mode
    CLS '                                                                 clear screen
    PLAY "l64o3ao2ao1ao3ao2ao1ao3ao2ao1a" '                               get developer's attention
    COLOR 12, 0
    PRINT '                                                               print error message
    PRINT " Controller Library has encountered the following error condition:"
    COLOR 15, 0
    PRINT
    PRINT " Error in routine: ";
    COLOR 14, 0
    PRINT __CURRENT_ROUTINE
    COLOR 15, 0
    PRINT " Previous routine: ";
    COLOR 14, 0
    PRINT __PREVIOUS_ROUTINE
    COLOR 11, 0
    PRINT
    PRINT " "; Message
    COLOR 7, 0
    _KEYCLEAR '                                                           clear all key buffers
    END '                                                                 terminate with "Press any key to continue..."

END SUB
' ______________________________________________________________________________________________________________________________________________
'/                                                                                                                                              \
FUNCTION IUO__ALREADY_ASSIGNED (Button AS TYPE__SLOT, Handle AS INTEGER, Slot AS INTEGER) '                               IUO__ALREADY_ASSIGNED |
    ' __________________________________________________________________________________________________________________________________________|____
    '/                                                                                                                                               \
    '| Returns -1 (TRUE) if a button is already assigned, 0 (FALSE) otherwise.                                                                       |
    '|                                                                                                                                               |
    '| IF IUO__ALREADY_ASSIGNED(Assigning, HandleFound, SlotFound)                                                                                   |
    '|                                                                                                                                               |
    '| Button - the new button being assigned                                                                                                        |
    '| Handle - the button handle that already contains this assignment                                                                              |
    '| Slot   - the slot number that already contains this assignment                                                                                |
    '|                                                                                                                                               |
    '| NOTE: IUO - Internal Use Only                                                                                                                 |
    '\_______________________________________________________________________________________________________________________________________________/

    SHARED CL_BUTTON() AS TYPE__BUTTON ' need access to button array

    IUO__ALREADY_ASSIGNED = 0 '                                       assume button is not preassigned
    Handle = 0 '                                                      reset button handle
    Slot = 0 '                                                        reset slot number
    DO '                                                              begin assignment search
        Handle = Handle + 1 '                                         increment handle counter
        IF CL_BUTTON(Handle).Slot1.cid = Button.cid THEN '            do the controller ids match in slot 1?
            IF CL_BUTTON(Handle).Slot1.Button = Button.Button THEN '  yes, do the buttons match?
                IF CL_BUTTON(Handle).Slot1.Axis = Button.Axis THEN '  yes, do the axes match?
                    Slot = 1 '                                        yes, slot 1 already has this button assigned
                END IF
            END IF
        END IF
        IF CL_BUTTON(Handle).Slot2.cid = Button.cid THEN
            IF CL_BUTTON(Handle).Slot2.Button = Button.Button THEN
                IF CL_BUTTON(Handle).Slot2.Axis = Button.Axis THEN
                    Slot = 2 '                                        slot 2 already has this button assigned
                END IF
            END IF
        END IF
        IF CL_BUTTON(Handle).Slot3.cid = Button.cid THEN
            IF CL_BUTTON(Handle).Slot3.Button = Button.Button THEN
                IF CL_BUTTON(Handle).Slot3.Axis = Button.Axis THEN
                    Slot = 3 '                                        slot 3 already has this button assigned
                END IF
            END IF
        END IF
        IF CL_BUTTON(Handle).Slot4.cid = Button.cid THEN
            IF CL_BUTTON(Handle).Slot4.Button = Button.Button THEN
                IF CL_BUTTON(Handle).Slot4.Axis = Button.Axis THEN
                    Slot = 4 '                                        slot 4 already has this button assigned
                END IF
            END IF
        END IF
    LOOP UNTIL (Handle = UBOUND(CL_BUTTON)) OR Slot '                 leave when all checked or a slot contains assignment
    IF Slot THEN IUO__ALREADY_ASSIGNED = -1 '                         report button already assigned

END FUNCTION


Code: (Select All)
'$INCLUDE:'CONTROLLER.BI'
'----------------------------------------------------------------------------------------------------
' Controller Library Example 1
' Creating user defined buttons that include keyboard keys and joystick axes
'
' Use the keyboard ARROW keys, keyboard WASD keys, or Joystick 1 to move the circle around the screen
'
' Terry Ritchie
' April 11th, 2023 - initial release
' May 17th, 2023   - updated to use Controller Library v1.10
'----------------------------------------------------------------------------------------------------

DIM UP_Button AS INTEGER '    user defined button handles with up to four associated controller buttons and/or axes
DIM DOWN_Button AS INTEGER
DIM LEFT_Button AS INTEGER
DIM RIGHT_Button AS INTEGER
DIM x AS INTEGER '            circle x location
DIM y AS INTEGER '            circle y location

__INITIALIZE_CONTROLLERS '                                 initialize controller library
__MAKE_BUTTON UP_Button
__MAKE_BUTTON DOWN_Button
__MAKE_BUTTON LEFT_Button
__MAKE_BUTTON RIGHT_Button

'+----------------------------------------------------------------------------------+
'| Assign keyboard ARROW keys, WASD keys, and joystick 1 to the four button handles |
'+----------------------------------------------------------------------------------+

__ASSIGN_BUTTON UP_Button, __KEYBOARDCID, CLKEY_UP '       keyboard UP ARROW key assigned to UP_Button                     [SLOT1]
__ASSIGN_BUTTON UP_Button, __KEYBOARDCID, CLKEY_W '        Keyboard W key also assigned to UP_Button                       [SLOT2]
__ASSIGN_AXIS UP_Button, __JOYPAD1CID, -2 '                joystick vertical axis UP (-) also assigned to UP_Button        [SLOT3]
__ASSIGN_BUTTON DOWN_Button, __KEYBOARDCID, CLKEY_DOWN '   keyboard DOWN ARROW key assigned to DOWN_Button                 [SLOT1]
__ASSIGN_BUTTON DOWN_Button, __KEYBOARDCID, CLKEY_S '      keyboard S key also assigned to DOWN_Button                     [SLOT2]
__ASSIGN_AXIS DOWN_Button, __JOYPAD1CID, 2 '               joystick vertical axis DOWN (+) also assigned to DOWN_Button    [SLOT3]
__ASSIGN_BUTTON LEFT_Button, __KEYBOARDCID, CLKEY_LEFT '   keyboard LEFT ARROW key assigned to LEFT_Button                 [SLOT1]
__ASSIGN_BUTTON LEFT_Button, __KEYBOARDCID, CLKEY_A '      keyboard A key also assigned to LEFT_Button                     [SLOT2]
__ASSIGN_AXIS LEFT_Button, __JOYPAD1CID, -1 '              joystick horizontal axis LEFT (-) also assigned to LEFT_Button  [SLOT3]
__ASSIGN_BUTTON RIGHT_Button, __KEYBOARDCID, CLKEY_RIGHT ' keyboard RIGHT ARROW key assigned to RIGHT_Button               [SLOT1]
__ASSIGN_BUTTON RIGHT_Button, __KEYBOARDCID, CLKEY_D '     keyboard D key also assigned to RIGHT_Button                    [SLOT2]
__ASSIGN_AXIS RIGHT_Button, __JOYPAD1CID, 1 '              joystick horizontal axis RIGHT (+) also asigned to RIGHT_Button [SLOT3]
SCREEN _NEWIMAGE(640, 480, 32)
x = 319
y = 239
DO
    _LIMIT 30
    CLS
    LOCATE 2, 1
    PRINT " Controller Library Demo 1"
    PRINT " Use ARROW keys, WASD keys, or Joystick 1"
    PRINT " ESC to exit"
    IF __BUTTON_DOWN(UP_Button) THEN y = y - 5 '           press UP ARROW    key, W key, or move joystick UP
    IF __BUTTON_DOWN(DOWN_Button) THEN y = y + 5 '         press DOWN ARROW  key, S key, or move joystick DOWN
    IF __BUTTON_DOWN(LEFT_Button) THEN x = x - 5 '         press LEFT ARROW  key, A key, or move joystick LEFT
    IF __BUTTON_DOWN(RIGHT_Button) THEN x = x + 5 '        press RIGHT ARROW key, D key, or move joystick RIGHT
    CIRCLE (x, y), 30
    _DISPLAY
LOOP UNTIL _KEYDOWN(27) '                                  exit when ESC pressed
SYSTEM

'$INCLUDE:'CONTROLLER.BM'


Code: (Select All)
'$INCLUDE:'CONTROLLER.BI'
'----------------------------------------------------------------------------------------------------
' Controller Library Example 2
' Remapping joystick axes
'
' Use Joystick 1 to move the circle around the screen
'
' Terry Ritchie
' April 11th, 2023 - original release
' May 17th, 2023   - updated to use Controller Library v1.10
'----------------------------------------------------------------------------------------------------

__INITIALIZE_CONTROLLERS '                                       initialize controller library
SCREEN _NEWIMAGE(640, 480, 32)
DO
    _LIMIT 30
    CLS
    LOCATE 2, 1
    PRINT " Controller Library Demo 2"
    PRINT " Use Joystick 1"
    PRINT " ESC to exit"
    x = __MAP_AXIS(__CONTROLLER_AXIS(__JOYPAD1CID, 1), 0, 639) ' joystick 1 axis now 0 to 639
    y = __MAP_AXIS(__CONTROLLER_AXIS(__JOYPAD1CID, 2), 0, 479) ' joystick 2 axis now 0 to 479
    CIRCLE (x, y), 30
    _DISPLAY
LOOP UNTIL _KEYDOWN(27) '                                        exit when ESC pressed
SYSTEM

'$INCLUDE:'CONTROLLER.BM'


Code: (Select All)
'$INCLUDE:'CONTROLLER.BI'
'----------------------------------------------------------------------------------------------------
' Controller Library Example 3
' Polling controllers for information
'
' Reports information on all controllers found.
'
' Terry Ritchie
' April 11th, 2023 - original release
' May 17th, 2023   - updated to use Controller Library v1.10
'----------------------------------------------------------------------------------------------------

__INITIALIZE_CONTROLLERS ' initialize controller library
DIM Joysticks AS INTEGER
DIM Format AS STRING
DIM j AS INTEGER
DIM jid AS INTEGER

Joysticks = __JOYPAD_EXISTS(1)
Format = " |       #        | \                             \ |   ###   |  ##  |"
CLS
PRINT
PRINT "  ___________________________________________________________________"
PRINT " /           CONTROLLERS FOUND CONNECTED TO YOUR COMPUTER            \"
PRINT " |----------------+---------------------------------+---------+------|"
PRINT " |  CONTROLLER ID |         CONTROLLER NAME         | BUTTONS | AXES |"
PRINT " |----------------+---------------------------------+---------+------|"
IF __KEYBOARD_EXISTS THEN PRINT USING Format; __KEYBOARDCID; __CONTROLLER_NAME$(__KEYBOARDCID); __BUTTON_TOTAL(__KEYBOARDCID); 0
PRINT " |----------------+---------------------------------+---------+------|"
IF __MOUSE_EXISTS THEN PRINT USING Format; __MOUSECID; __CONTROLLER_NAME$(__MOUSECID); __BUTTON_TOTAL(__MOUSECID); __AXIS_TOTAL(__MOUSECID)
FOR j = 1 TO Joysticks
    SELECT CASE j
        CASE 1: jid = __JOYPAD1CID
        CASE 2: jid = __JOYPAD2CID
        CASE 3: jid = __JOYPAD3CID
        CASE 4: jid = __JOYPAD4CID
        CASE 5: jid = __JOYPAD5CID
        CASE 6: jid = __JOYPAD6CID
    END SELECT
    PRINT " |----------------+---------------------------------+---------+------|"
    PRINT USING Format; jid; __CONTROLLER_NAME$(jid); __BUTTON_TOTAL(jid); __AXIS_TOTAL(jid)
NEXT j
PRINT " \________________|_________________________________|_________|______/"
PRINT
PRINT " Press ESC to exit"
SLEEP
SYSTEM

'$INCLUDE:'CONTROLLER.BM'



RE: Controller Library - TempodiBasic - 04-13-2023

Hi Terry
fine to see another your masterpiece tool

this is demo1 screenshot 


[Image: immagine-2023-04-13-232612456.png]

this comes out running Demo2

[Image: immagine-2023-04-13-233022205.png]


and this is the result  of demo3


[Image: immagine-2023-04-13-233357190.png]


What to say: wonderful library!
Thanks to share it.
I hope to see many games using Controller Library for managing easily the input dispositives.


RE: Controller Library - TerryRitchie - 04-13-2023

Thank you for trying it out. Let me know if you find anything that needs addressed. I'll get to adding wheel support in a bit as soon as I figure out why my code isn't working.


RE: Controller Library - OldMoses - 04-16-2023

I love the look of the code, with those ASCII picture comments of the keyboard. Cool stuff there, I've always been impressed by how you comment your code.


RE: Controller Library - TerryRitchie - 04-17-2023

(04-16-2023, 04:35 PM)OldMoses Wrote: I love the look of the code, with those ASCII picture comments of the keyboard. Cool stuff there, I've always been impressed by how you comment your code.

Thank you. My comments are just as much for me as they are for others reading my code (probably more for me as I get older!). As every coder knows going back to code you've written a few years back, heck sometimes even a few months back, can make you ponder, "What the heck was I thinking there?"

It only takes a few more minutes to document code well and you'll thank yourself in the long term. I can go back to any of my old code and find routines and methods I want to reuse without having to spend hours studying the code to re-learn what I already did.

It's hard to get into the habit of commenting but now I find I like writing the comments just as much as writing the code. I'm so used to it that it feels strange to me not to comment now.

When I was in the Marine Corps I had a 4 month gap between deployments. A three month 300 level advanced course in x86 Assembler at the base college just so happened to be starting. The professor (wish I could remember his name) commented his code so well you could read it like a book and know exactly what was going on. That's when I decided commenting was just as important as coding. Commenting is an absolute must in Assembler.


RE: Controller Library - OldMoses - 04-17-2023

My commenting style is constantly in flux as I refine my coding skills and procedures, but yours has been a strong influence in what I've tried to incorporate in my code. My magnum opus project, being an almost 6K line behemoth, I decided to make commenting a central part of it. I couldn't possibly begin to work on it after a hiatus without those reminders. Sometimes I spend entire coding sessions tearing out and revising just the comments. Sounds a bit pedantic and wasteful perhaps, but I've heard of worse in some projects, and just occasionally I'll see optimization opportunities in the code while doing it.


RE: Controller Library - TerryRitchie - 04-26-2023

I created a small simulated game that allows the player to reconfigure buttons as a demo to show others how this can be done. The program source code below along with the assets (sounds, graphics, and library files) are now included with the library. I updated the first post with a new ZIP file that you can download.

Also, the library is now at version 1.01. I found some bone head logic mistakes I made and corrected them.

Code: (Select All)
'------------------------------
' Controller Library Demo
' "Reconfiguring Buttons"
'
' Terry Ritchie
' April 26th, 2023
'------------------------------
' Default Keys:
'
'   W      - UP    (Mario JUMP)
'   S      - DOWN  (Mario CROUCH)
'   A      - Mario LEFT
'   D      - Mario RIGHT
'   C      - Mario CROUCH
'   SPACE  - Mario JUMP
'   RCTRL  - Mario FIRE
'   RSHIFT - Mario RUN
'
' Hitting mystery box on bottom
' releases coins.
'
'------------------------------

'$INCLUDE:'CONTROLLER.BI'

OPTION _EXPLICIT '                all variables must be explicitly defined

CONST STAND = 1 '                 action sprites - Standing
CONST CROUCH = 2 '                crouching
CONST RUNSTART = 3 '              get ready to run
CONST WALK = 4 '                  walking/running (6 animation images)
CONST JUMP = 5 '                  jumping
CONST FIRE = 6 '                  bullet sprites (4 animation images)
CONST COIN = 7 '                  coin sprite

TYPE TYPE_RECTANGLE '             RECTANGLE PROPERTIES
    x1 AS INTEGER '               upper left x
    y1 AS INTEGER '               upper left y
    x2 AS INTEGER '               lower right x
    y2 AS INTEGER '               lower right y
END TYPE

TYPE TYPE_BUTTON '                DEFINED BUTTON PROPERTIES
    UP AS INTEGER '               UP button(s)
    DOWN AS INTEGER '             DOWN button(s)
    RIGHT AS INTEGER '            RIGHT button(s)
    LEFT AS INTEGER '             LEFT buttons(s)
    FIRE AS INTEGER '             FIRE button(s)
    RUN AS INTEGER '              RUN button(s)
    JUMP AS INTEGER '             JUMP button(s)
    CROUCH AS INTEGER '           CROUCH button(s)
    F1 AS INTEGER '               F1 button (key - enter config mode)
    F2 AS INTEGER '               F2 button (key - reset button defaults)
    F3 AS INTEGER '               F3 button (key - leave config mode)
END TYPE

TYPE TYPE_MARIO '                 MARIO PROPERTIES
    x AS SINGLE '                 x location (center bottom)
    y AS SINGLE '                 y location (center bottom)
    Frame AS INTEGER '            animation frame
    Action AS INTEGER '           STAND, CROUCH, RUNSTART, WALK, JUMP
    Rect AS TYPE_RECTANGLE '      sprite image location
    FPS AS INTEGER '              mario animation update speed
    Direction AS INTEGER '        direction mario is traveling
    Speed AS INTEGER '            speed of mario (walking or running)
    Facing AS INTEGER '           direction mario is facing
    Jumping AS INTEGER '          mario currently jumping (t/f)
    Running AS INTEGER '          mario currently running (t/f)
    Walking AS INTEGER '          mario currently walking (t/f)
    RunTimer AS INTEGER '         period to show ready to run image
    Yvel AS SINGLE '              y velocity of mario jump
    Vector AS SINGLE '            y vector of mario jump
    Floor AS INTEGER '            lowest point on screen mario can go
END TYPE

TYPE TYPE_BULLET '                BULLET PROPERTIES
    InUse AS INTEGER '            this bullet currently active (t/f)
    x AS SINGLE '                 x coordinate of bullet
    y AS SINGLE '                 y coordinate of bullet
    Direction AS INTEGER '        direction bullet is traveling
    Cell AS INTEGER '             current bullet animation cell
END TYPE

TYPE TYPE_COIN '                  COIN PROPERTIES
    InUse AS INTEGER '            this coin currently active (t/f)
    y AS SINGLE '                 y coordinate of coin
    vector AS SINGLE '            y vector of coin
END TYPE

'+------------------------------+
'| Program variable definitions |
'+------------------------------+

REDIM Bullets(1) AS TYPE_BULLET ' bullet array
REDIM Coins(1) AS TYPE_COIN '     coin array
DIM Button AS TYPE_BUTTON '       player buttons
DIM WorkScreen AS LONG '          graphics drawn here
DIM BackGround AS LONG '          static background image
DIM Sprite(7, 6) AS LONG '        mario sprite image pool
DIM Mario AS TYPE_MARIO '         mario properties
DIM Box AS TYPE_RECTANGLE '       mystery box coordinates
DIM MysteryBox AS LONG '          mystery box image
DIM FPSFrame AS INTEGER '         current frame (1 to 30)
DIM BulletTimer AS INTEGER '      time to wait between bullets
DIM SNDBump AS LONG '             bump sound
DIM SNDCoin AS LONG '             coin sound
DIM SNDConf AS LONG '             configuration screen sound
DIM SNDFire AS LONG '             bullet firing sound
DIM SNDJump AS LONG '             jump sound
DIM SNDSong AS LONG '             theme song

'______________________________________________________________________________________________________________________

'+---------------+
'| Program setup |
'+---------------+

__INITIALIZE_CONTROLLERS '             controller library initialize
SET_DEFAULT_BUTTONS '                  set default input buttons
LOAD_ASSETS '                          load demo graphics and sounds
INITIALIZE_VARIABLES '                 initialize demo variables
SCREEN _NEWIMAGE(640, 480, 32) '       create view screen
_SNDLOOP SNDSong '                     play theme song

'+--------------------------+
'| Main program loop begins |
'+--------------------------+

DO '                                   begin demo loop
    _LIMIT 30 '                        30 frames per second
    IF __BUTTON_DOWN(Button.F1) THEN ' player press F1 key?
        CONFIGURE_BUTTONS '            yes, configure buttons
    END IF
    UPDATE_MARIO '                     update mario position
    DRAW_SCREEN '                      draw demo screen
    MOVE_MARIO '                       check for player inputs
LOOP UNTIL _KEYDOWN(27) '              leave demo when ESC pressed

'+-------------------------------+
'| Free assets from RAM and exit |
'+-------------------------------+

CLEANUP '                              remove all assets from memory
SYSTEM '                               return to OS

'______________________________________________________________________________________________________________________


' _____________________________________________________________________________________________________________________________________________
'/                                                                                                                                             \
SUB CONFIGURE_BUTTONS () '                                                                                                   CONFIGURE_BUTTONS |
    ' _________________________________________________________________________________________________________________________________________|____
    '/                                                                                                                                              \
    '| Allows player to reconfigure keyboard keys, joystick/game pad buttons, and joystick/game pad axes as buttons.                                |
    '\______________________________________________________________________________________________________________________________________________/

    SHARED Button AS TYPE_BUTTON ' need access to buttons
    SHARED SNDSong AS LONG '       need access to theme song
    SHARED SNDConf AS LONG '       need access to configure sound

    DIM Menu(8) AS STRING '        horizontal line of menu text
    DIM MenuLine AS INTEGER '      current highlighted menu line
    DIM CreateMenu AS INTEGER '    -1 when menu needs recreating
    DIM m AS INTEGER '             menu counter
    DIM s AS INTEGER '             slot counter
    DIM KeyHit AS INTEGER '        player keyboard input using _KEYHIT
    DIM Hit AS INTEGER '           player keyboard input using INKEY$

    __SET_AXIS_THRESHOLD .9 '                                                       set axis threshold to register as a button press
    __BUTTON_REASSIGN_ALLOWED '                                                     allow reassigning of buttons
    _SNDSTOP SNDSong '                                                              stop theme song
    _SNDPLAY SNDConf '                                                              play configure sound
    MenuLine = 1 '                                                                  highlight starts on line 1
    CreateMenu = -1 '                                                               menu needs creating
    DO '                                                                            begin main loop
        _LIMIT 30 '                                                                 don't hog the CPU
        CLS , _RGB32(92, 148, 252) '                                                clear screen with light blue
        IF __BUTTON_DOWN(Button.F2) THEN '                                          was F2 key pressed?
            SET_DEFAULT_BUTTONS '                                                   yes, set all buttons to default values
            CreateMenu = -1 '                                                       menu needs recreated
            _SNDPLAY SNDConf '                                                      play configure sound
        END IF
        IF CreateMenu THEN '                                                        does menu need (re)created?
            CreateMenu = 0 '                                                        yes, reset creation flag
            Menu(1) = "  UP      " '                                                create beginning of each menu item
            Menu(2) = "  DOWN    "
            Menu(3) = "  LEFT    "
            Menu(4) = "  RIGHT   "
            Menu(5) = "  JUMP    "
            Menu(6) = "  FIRE    "
            Menu(7) = "  RUN     "
            Menu(8) = "  CROUCH  "
            FOR s = 1 TO 4 '                                                        cycle through 4 slots
                Menu(1) = Menu(1) + " " + __BUTTON_NAME$(Button.UP, s) + " " '      add assigned button names to each menu line
                Menu(2) = Menu(2) + " " + __BUTTON_NAME$(Button.DOWN, s) + " "
                Menu(3) = Menu(3) + " " + __BUTTON_NAME$(Button.LEFT, s) + " "
                Menu(4) = Menu(4) + " " + __BUTTON_NAME$(Button.RIGHT, s) + " "
                Menu(5) = Menu(5) + " " + __BUTTON_NAME$(Button.JUMP, s) + " "
                Menu(6) = Menu(6) + " " + __BUTTON_NAME$(Button.FIRE, s) + " "
                Menu(7) = Menu(7) + " " + __BUTTON_NAME$(Button.RUN, s) + " "
                Menu(8) = Menu(8) + " " + __BUTTON_NAME$(Button.CROUCH, s) + " "
            NEXT s
        END IF
        COLOR _RGB32(255, 255, 0), _RGB32(0, 0, 255) '                              yellow text on blue background
        _PRINTMODE _FILLBACKGROUND '                                                show the background color
        LOCATE 2, 25 '                                                              position cursor
        PRINT "                             " '                                     print header
        LOCATE 3, 25
        PRINT " CONFIGURE CONTROLLER INPUTS "
        LOCATE 4, 25
        PRINT "                             "
        LINE (192, 16)-(423, 63), _RGB32(255, 255, 0), B '                          draw a box around header
        COLOR _RGB32(0, 0, 0) '                                                     black text
        _PRINTMODE _KEEPBACKGROUND '                                                transparent text background
        LOCATE 6, 6 '                                                               draw input instructions
        PRINT "UP/DOWN: Select Action      DEL: Remove Button    F2: Reset Defaults"
        LOCATE 7, 6
        PRINT "ENTER  : Configure Button                         F3: Return to Game"
        LOCATE 9, 1
        PRINT
        PRINT "   ACTION      METHOD 1         METHOD 2         METHOD 3         METHOD 4     "
        PRINT
        FOR m = 1 TO 8 '                                                            cycle through 8 menu lines
            PRINT " ";
            IF m = MenuLine THEN '                                                  is this menu line highlighted?
                COLOR _RGB32(255, 255, 0), _RGB32(0, 0, 255) '                      yes, yellow text on blue background
                _PRINTMODE _FILLBACKGROUND '                                        show the background color
            END IF
            PRINT Menu(m) '                                                         print the menu line
            COLOR _RGB32(0, 0, 0) '                                                 black text
            _PRINTMODE _KEEPBACKGROUND '                                            transparent text background
            PRINT
        NEXT m
        FOR m = 135 TO 391 STEP 32 '                                                draw grid lines around menu items
            LINE (8, m)-(632, m + 32), _RGB32(0, 0, 0), B
        NEXT m
        LINE (83, 135)-(83, 423), _RGB32(0, 0, 0)
        LINE (219, 135)-(219, 423), _RGB32(0, 0, 0)
        LINE (355, 135)-(355, 423), _RGB32(0, 0, 0)
        LINE (491, 135)-(491, 423), _RGB32(0, 0, 0)
        KeyHit = _KEYHIT '                                                          get any key pressed by player
        IF KeyHit = 18432 THEN '                                                    was the UP ARROW key pressed?
            MenuLine = MenuLine - 1 '                                               yes, move highlight up one line
            IF MenuLine = 0 THEN MenuLine = 1 '                                     keep highlight at top if necessary
        ELSEIF KeyHit = 20480 THEN '                                                no, was the DOWN ARROW key pressed?
            MenuLine = MenuLine + 1 '                                               yes, move highlight down one line
            IF MenuLine = 9 THEN MenuLine = 8 '                                     keep highlight at bottom if necessary
        ELSEIF KeyHit = 21248 THEN '                                                no, was the DELETE key pressed?
            COLOR _RGB32(255, 255, 0), _RGB32(255, 0, 0) '                          yes, yellow text on red background
            _PRINTMODE _FILLBACKGROUND '                                            show the background color
            LOCATE 29, 15 '                                                         position cursor
            PRINT " PRESS 1, 2, 3, or 4 TO REMOVE METHOD FROM ACTION "; '           display instructions
            _DISPLAY '                                                              update screen to show instructions
            DO '                                                                    begin key press loop
                _LIMIT 30 '                                                         don't hog the CPU
                Hit = VAL(INKEY$) '                                                 get value of any key pressed
            LOOP UNTIL Hit '                                                        leave when value not equal to zero
            IF Hit >= 1 AND Hit <= 4 THEN '                                         is value between 1 and 4?
                CreateMenu = -1 '                                                   yes, the menu will need recreated
                SELECT CASE MenuLine '                                              which menu line is highlighted?
                    CASE 1 '                                                        line 1
                        __REMOVE_BUTTON Button.UP, Hit '                            remove button from chosen slot
                    CASE 2 '                                                        line 2
                        __REMOVE_BUTTON Button.DOWN, Hit '                          remove button from chosen slot
                    CASE 3 '                                                        line 3
                        __REMOVE_BUTTON Button.LEFT, Hit '                          etc..
                    CASE 4
                        __REMOVE_BUTTON Button.RIGHT, Hit
                    CASE 5
                        __REMOVE_BUTTON Button.JUMP, Hit
                    CASE 6
                        __REMOVE_BUTTON Button.FIRE, Hit
                    CASE 7
                        __REMOVE_BUTTON Button.RUN, Hit
                    CASE 8
                        __REMOVE_BUTTON Button.CROUCH, Hit
                END SELECT
                _SNDPLAY SNDConf '                                                  play configure sound
            END IF
            _KEYCLEAR '                                                             clear all keyboard buffers
        ELSEIF KeyHit = 13 THEN '                                                   no, was the ENTER key pressed?
            COLOR _RGB32(255, 255, 0), _RGB32(255, 0, 0) '                          yes, yellow text on red background
            _PRINTMODE _FILLBACKGROUND '                                            show background color
            LOCATE 29, 12 '                                                         position cursor
            PRINT " SELECT KEYBOARD KEY, JOYSTICK AXIS, OR JOYSTICK BUTTON "; '     display instructions
            _DISPLAY '                                                              update display to show instructions
            CreateMenu = -1 '                                                       the menu will need recreated
            SELECT CASE MenuLine '                                                  which menu line is highlighted?
                CASE 1 '                                                            line 1
                    __AUTOASSIGN_BUTTON Button.UP '                                 assign button/axis to UP button
                CASE 2 '                                                            line 2
                    __AUTOASSIGN_BUTTON Button.DOWN '                               assign button/axis to DOWN button
                CASE 3 '                                                            line 3
                    __AUTOASSIGN_BUTTON Button.LEFT '                               etc..
                CASE 4
                    __AUTOASSIGN_BUTTON Button.RIGHT
                CASE 5
                    __AUTOASSIGN_BUTTON Button.JUMP
                CASE 6
                    __AUTOASSIGN_BUTTON Button.FIRE
                CASE 7
                    __AUTOASSIGN_BUTTON Button.RUN
                CASE 8
                    __AUTOASSIGN_BUTTON Button.CROUCH
            END SELECT
            _SNDPLAY SNDConf '                                                      play configure sound
        END IF
        _DISPLAY '                                                                  update screen with changes
    LOOP UNTIL __BUTTON_DOWN(Button.F3) '                                           leave when F3 key pressed
    _SNDLOOP SNDSong '                                                              start theme song again

END SUB
' _____________________________________________________________________________________________________________________________________________
'/                                                                                                                                             \
SUB CLEANUP () '                                                                                                                       CLEANUP |
    ' _________________________________________________________________________________________________________________________________________|____
    '/                                                                                                                                              \
    '| Removes all sounds and graphics from RAM. (Always clean up after yourself before exiting program)                                            |
    '\______________________________________________________________________________________________________________________________________________/

    SHARED Sprite() AS LONG '   need access to sprite images
    SHARED WorkScreen AS LONG ' need access to work screen
    SHARED BackGround AS LONG ' need access to background image
    SHARED MysteryBox AS LONG ' need access to mystery box image
    SHARED SNDBump AS LONG '    need access to all sounds
    SHARED SNDCoin AS LONG
    SHARED SNDConf AS LONG
    SHARED SNDFire AS LONG
    SHARED SNDJump AS LONG
    SHARED SNDSong AS LONG
    DIM i AS INTEGER '          generic counter
    DIM j AS INTEGER '          generic counter

    SCREEN 0, 0, 0, 0 '                                    go to pure text screen
    CLS '                                                  clear screen
    _SNDCLOSE SNDBump '                                    remove sounds from RAM
    _SNDCLOSE SNDCoin
    _SNDCLOSE SNDFire
    _SNDCLOSE SNDJump
    _SNDCLOSE SNDSong
    _SNDCLOSE SNDConf
    _FREEIMAGE WorkScreen '                                remove images from RAM
    _FREEIMAGE BackGround
    _FREEIMAGE MysteryBox
    FOR i = 1 TO 7
        FOR j = 1 TO 6
            IF Sprite(i, j) THEN _FREEIMAGE Sprite(i, j) ' remove sprite images from RAM
        NEXT j
    NEXT i

END SUB
' _____________________________________________________________________________________________________________________________________________
'/                                                                                                                                             \
SUB SPAWN_COIN () '                                                                                                                 SPAWN_COIN |
    ' _________________________________________________________________________________________________________________________________________|____
    '/                                                                                                                                              \
    '| Spawns a coin.                                                                                                                               |
    '\______________________________________________________________________________________________________________________________________________/

    SHARED Coins() AS TYPE_COIN '  need access to coin array
    SHARED Box AS TYPE_RECTANGLE ' need access to mystery box coordinates
    SHARED SNDCoin AS LONG '       need access to coin sound
    DIM c AS INTEGER '             coin counter

    c = 0 '                                              reset coin counter
    DO '                                                 loop through coin array
        c = c + 1 '                                      increment coin counter
    LOOP UNTIL Coins(c).InUse = 0 OR c = UBOUND(Coins) ' leave when array scannded
    IF Coins(c).InUse THEN '                             if index in use then all indexes used
        c = c + 1 '                                      increment coin counter
        REDIM _PRESERVE Coins(c) AS TYPE_COIN '          increase size of coin array
    END IF
    _SNDPLAY SNDCoin '                                   play coin sound
    Coins(c).InUse = -1 '                                mark this index in use
    Coins(c).y = Box.y1 + 7 '                            calculate y coordinate of coin
    Coins(c).vector = -5 '                               set initial vertical vector

END SUB
' _____________________________________________________________________________________________________________________________________________
'/                                                                                                                                             \
SUB UPDATE_COINS () '                                                                                                             UPDATE_COINS |
    ' _________________________________________________________________________________________________________________________________________|____
    '/                                                                                                                                              \
    '| Updates the position of active coins and draws them to the screen.                                                                           |
    '\______________________________________________________________________________________________________________________________________________/

    SHARED Sprite() AS LONG '      need access to sprite images
    SHARED Coins() AS TYPE_COIN '  need access to coin array
    SHARED Box AS TYPE_RECTANGLE ' need access to mystery box coordinates
    DIM c AS INTEGER '             coin counter

    c = 0 '                                                    reset coin counter
    DO '                                                       loop through coin array
        c = c + 1 '                                            increment coin counter
        IF Coins(c).InUse THEN '                               is this coin in use?
            Coins(c).y = Coins(c).y + Coins(c).vector '        yes, update coin y coordinate
            Coins(c).vector = Coins(c).vector * .91 '          decrease vertical vector amount slightly
            _PUTIMAGE (Box.x1, Coins(c).y), Sprite(COIN, 1) '  draw coin to screen
            IF Coins(c).y < 1 THEN Coins(c).InUse = 0 '        deactivate coin when it reaches top of screen
        END IF
    LOOP UNTIL c = UBOUND(Coins) '                             leave when array scanned

END SUB
' _____________________________________________________________________________________________________________________________________________
'/                                                                                                                                             \
SUB DRAW_SCREEN () '                                                                                                               DRAW_SCREEN |
    ' _________________________________________________________________________________________________________________________________________|____
    '/                                                                                                                                              \
    '| Brings all the components together to create one frame of the demo.                                                                          |
    '\______________________________________________________________________________________________________________________________________________/

    SHARED Sprite() AS LONG '    need access to sprite images
    SHARED Mario AS TYPE_MARIO ' need access to mario properties
    SHARED WorkScreen AS LONG '  need access to the work screen
    SHARED BackGround AS LONG '  need access to the background image
    SHARED MysteryBox AS LONG '  need access to the mystery box image

    '+--------------------+
    '| Update view screen |
    '+--------------------+

    _DEST WorkScreen '                                                                                               draw on work screen
    _PUTIMAGE , BackGround, WorkScreen '                                                                             erase work screen with background
    _PRINTMODE _KEEPBACKGROUND '                                                                                     transparent text background
    LOCATE 1, 6 '                                                                                                    position cursor
    COLOR _RGB32(0, 0, 0) '                                                                                          black text
    PRINT "PRESS F1 TO CONFIGURE BUTTONS" '                                                                          print instructions
    IF Mario.Facing = -1 THEN '                                                                                      is mario facing left?
        _PUTIMAGE (Mario.Rect.x2, Mario.Rect.y1)-(Mario.Rect.x1, Mario.Rect.y2), Sprite(Mario.Action, Mario.Frame) ' yes, flip sprite horizontally
    ELSE '                                                                                                           no, mario is facing right
        _PUTIMAGE (Mario.Rect.x1, Mario.Rect.y1), Sprite(Mario.Action, Mario.Frame) '                                draw sprite as drawn
    END IF
    UPDATE_BULLETS '                                                                                                 draw active bullets
    UPDATE_COINS '                                                                                                   draw active coins
    _PUTIMAGE (144, 48), MysteryBox '                                                                                draw mystery box
    _DEST 0 '                                                                                                        draw on view screen
    _PUTIMAGE , WorkScreen '                                                                                         stretch work screen (zoom 2X)
    _DISPLAY '                                                                                                       update view screen with changes

END SUB
' _____________________________________________________________________________________________________________________________________________
'/                                                                                                                                             \
SUB UPDATE_MARIO () '                                                                                                             UPDATE_MARIO |
    ' _________________________________________________________________________________________________________________________________________|____
    '/                                                                                                                                              \
    '| Updates mario's position on screen and checks for collision between mario and mystery box.                                                   |
    '\______________________________________________________________________________________________________________________________________________/

    SHARED Mario AS TYPE_MARIO '   need access to mario properties
    SHARED Box AS TYPE_RECTANGLE ' need access to mystery box coordinates
    SHARED FPSFrame AS INTEGER '   need access to master frame counter
    SHARED SNDBump AS LONG '       need access to bump sound

    FPSFrame = FPSFrame + 1 '                                                  increment master frame counter
    IF FPSFrame = 31 THEN FPSFrame = 1 '                                       reset master frame counter when needed
    Mario.x = Mario.x + Mario.Direction * Mario.Speed '                        update mario x position
    IF Mario.x >= 336 THEN Mario.x = Mario.x - 336 '                           move mario to left side of screen
    IF Mario.x <= -16 THEN Mario.x = Mario.x + 336 '                           move mario to right side of screen

    '+---------------------------------+
    '| Calculate mario sprite location |
    '+---------------------------------+

    Mario.Rect.x1 = Mario.x - 15 '                                             calculate mario rectangular coordinates
    Mario.Rect.y1 = Mario.y - 63
    Mario.Rect.x2 = Mario.Rect.x1 + 31
    Mario.Rect.y2 = Mario.Rect.y1 + 63
    IF COLLISION(Mario.Rect, Box) THEN '                                       has mario collided with mystery box?
        _SNDPLAY SNDBump '                                                     play bump sound

        '+-------------------------------------+
        '| Mario has collided with mystery box |
        '+-------------------------------------+

        IF Mario.x < Box.x1 - 8 THEN '                                         yes, is mario at left side of box?

            '+----------------------------+
            '| Mario hit left side of box |
            '+----------------------------+

            Mario.Direction = 0 '                                              yes, stop mario movement
            Mario.x = Box.x1 - 17 '                                            position mario at left side of box
        ELSEIF Mario.x > Box.x2 + 8 THEN '                                     no, is mario at right side of box?

            '+-----------------------------+
            '| Mario hit right side of box |
            '+-----------------------------+

            Mario.Direction = 0 '                                              yes, stop mario movement
            Mario.x = Box.x2 + 16 '                                            position mario at right side of box
        ELSE '                                                                 no, must have hit box from underneath

            '+-------------------------------+
            '| Mario hit box from underneath |
            '+-------------------------------+

            SPAWN_COIN '                                                       create a coin
            Mario.y = Box.y2 + 64 '                                            position mario just underneath box
            Mario.Yvel = 0 '                                                   stop upward movement

        END IF

        '+-----------------------------------+
        '| Recalculate mario sprite location |
        '+-----------------------------------+

        Mario.Rect.x1 = Mario.x - 15 '                                         recalculate mario rectangular coordinates
        Mario.Rect.y1 = Mario.y - 63
        Mario.Rect.x2 = Mario.Rect.x1 + 31
        Mario.Rect.y2 = Mario.Rect.y1 + 63
    END IF

END SUB
' _____________________________________________________________________________________________________________________________________________
'/                                                                                                                                             \
SUB MOVE_MARIO () '                                                                                                                 MOVE_MARIO |
    ' _________________________________________________________________________________________________________________________________________|____
    '/                                                                                                                                              \
    '| Moves mario according to player input.                                                                                                       |
    '\______________________________________________________________________________________________________________________________________________/

    SHARED Mario AS TYPE_MARIO '   need access to mario properties
    SHARED Button AS TYPE_BUTTON ' need access to buttons
    SHARED FPSFrame AS INTEGER '   need access to master frame counter
    SHARED SNDJump AS LONG '       need access to jump sound

    '+-------------------------------+
    '| Check for button/axis presses |
    '+-------------------------------+

    IF __BUTTON_DOWN(Button.FIRE) THEN FIRE_BULLET '                           fire a bullet
    IF NOT Mario.Jumping THEN '                                                is mario currently jumping?

        '+---------------------------------------------------+
        '| Must not be jumping to walk, run, jump, or crouch |
        '+---------------------------------------------------+

        IF __BUTTON_DOWN(Button.LEFT) THEN '                                   no, is player pressing a left button?

            '+--------------------+
            '| Walk and face left |
            '+--------------------+

            Mario.Walking = -1 '                                               yes, remember that mario is walking
            Mario.Direction = -1 '                                             mario is walking to the left
            Mario.Facing = -1 '                                                mario is facing left
            Mario.Action = WALK '                                              use walking sprites
            IF FPSFrame MOD Mario.FPS = 0 THEN Mario.Frame = Mario.Frame + 1 ' increment to next walking animation frame
            IF Mario.Frame = 7 THEN Mario.Frame = 1 '                          reset animation frame counter when necessary
        ELSEIF __BUTTON_DOWN(Button.RIGHT) THEN '                              no, is player pressing a right button?

            '+---------------------+
            '| Walk and face right |
            '+---------------------+

            Mario.Walking = -1 '                                               yes, remember that mario is walking
            Mario.Direction = 1 '                                              mario is walking to the right
            Mario.Facing = 1 '                                                 mario is facing right
            Mario.Action = WALK '                                              use walking sprites
            IF FPSFrame MOD Mario.FPS = 0 THEN Mario.Frame = Mario.Frame + 1 ' increment to next walking animation frame
            IF Mario.Frame = 7 THEN Mario.Frame = 1 '                          reset animation frame counter when necessary
        ELSE '                                                                 no, mario is standing still

            '+--------------+
            '| Stop walking |
            '+--------------+

            Mario.Action = STAND '                                             use standing sprite
            Mario.Frame = 1 '                                                  reset animation frame counter (sprite only has 1 frame)
            Mario.Direction = 0 '                                              mario is standing still
            Mario.Walking = 0 '                                                remember that mario is not walking
        END IF
        IF Mario.Walking THEN '                                                is mario currently walking?

            '+--------------------------------+
            '| Must be walking before running |
            '+--------------------------------+

            IF __BUTTON_DOWN(Button.RUN) THEN '                                yes, is player pressing a run button?

                '+---------------+
                '| Start running |
                '+---------------+

                IF NOT Mario.Running THEN '                                    yes, is mario already running?
                    Mario.Action = RUNSTART '                                  no, use getting ready to run sprite
                    Mario.Frame = 1 '                                          reset animation frame counter (sprite only has 1 frame)
                    Mario.RunTimer = Mario.RunTimer + 1 '                      increment getting ready to run timer
                    IF Mario.RunTimer = 5 THEN Mario.Running = -1 '            start mario running after 5 frames have passed
                    Mario.Direction = 0 '                                      mario stands still during these 5 frames
                ELSE '                                                         yes, mario is already running
                    Mario.Speed = 4 '                                          set mario speed
                    Mario.FPS = 2 '                                            set animation frames per second (15 FPS)
                END IF
            ELSE '                                                             no, a run button is not being pressed

                '+--------------+
                '| Stop running |
                '+--------------+

                Mario.Speed = 2 '                                              set mario speed
                Mario.FPS = 5 '                                                set animation frames per second (6 FPS)
                Mario.Running = 0 '                                            mario is no longer running
                Mario.RunTimer = 0 '                                           reset getting ready to run timer
            END IF
        END IF
        IF __BUTTON_DOWN(Button.CROUCH) OR __BUTTON_DOWN(Button.DOWN) THEN '   is player pressing a button to crouch?
            Mario.Action = CROUCH '                                            yes, use crouching sprite
            Mario.Frame = 1 '                                                  reset animation frame counter (sprite only has 1 frame)
            Mario.Direction = 0 '                                              mario is standing still
        ELSEIF __BUTTON_DOWN(Button.JUMP) OR __BUTTON_DOWN(Button.UP) THEN '   no, is player pressing a button to jump?
            _SNDPLAY SNDJump '                                                 play jump sound
            Mario.Jumping = -1 '                                               yes, mario is now jumping
            Mario.Yvel = -5 '                                                  set vertical velocity
            Mario.Vector = -1 '                                                set vertical vector direction
            Mario.Action = JUMP '                                              use jumping sprite
            Mario.Frame = 1 '                                                  reset animation frame counter (sprite only has 1 frame)
        END IF
    ELSE '                                                                     yes, mario is currently jumping

        '+---------------------------+
        '| Perform jump arc sequence |
        '+---------------------------+

        Mario.Vector = Mario.Vector + .2 '                                     increment vertical vector direction (will change direction at 0)
        Mario.Yvel = Mario.Yvel + Mario.Vector '                               add vector quantity to vertical velocity
        Mario.y = Mario.y + Mario.Yvel '                                       add vertical velocity to mario y coordinate
        IF Mario.y >= Mario.Floor THEN '                                       has mario hit floor on way down?

            '+--------------+
            '| Stop jumping |
            '+--------------+

            Mario.Jumping = 0 '                                                yes, mario is no longer jumping
            Mario.y = Mario.Floor '                                            place mario onto floor
        END IF
    END IF

END SUB
' _____________________________________________________________________________________________________________________________________________
'/                                                                                                                                             \
SUB UPDATE_BULLETS () '                                                                                                         UPDATE_BULLETS |
    ' _________________________________________________________________________________________________________________________________________|____
    '/                                                                                                                                              \
    '| Updates location of active bullets and draws them to the screen.                                                                             |
    '\______________________________________________________________________________________________________________________________________________/

    SHARED Mario AS TYPE_MARIO '       need access to mario properties
    SHARED Sprite() AS LONG '          need access to sprite images
    SHARED Bullets() AS TYPE_BULLET '  need access to bullet array
    SHARED BulletTimer AS INTEGER '    need access to bullet timer
    DIM b AS INTEGER '                 bullet counter

    IF BulletTimer THEN BulletTimer = BulletTimer - 1 '                                           decrement bullet timer if needed
    b = 0 '                                                                                       reset bullet counter
    DO '                                                                                          loop through bullet array
        b = b + 1 '                                                                               increment bullet counter
        IF Bullets(b).InUse THEN '                                                                is this bullet active?
            Bullets(b).x = Bullets(b).x + Bullets(b).Direction * 6 '                              yes, update bullet x coordinate
            IF Bullets(b).y < Mario.Floor - 9 THEN '                                              has bullet reached the floor?
                Bullets(b).y = Bullets(b).y + 3 '                                                 no, add to the bullet's y coordinate
            ELSE '                                                                                yes, bullet at floor
                Bullets(b).y = Bullets(b).y - 4 '                                                 bounce bullet back up
            END IF
            IF Bullets(b).x > 336 OR Bullets(b).x < -16 THEN '                                    has bullet left screen?
                Bullets(b).InUse = 0 '                                                            yes, this bullet no longer active
            ELSE '                                                                                no, bullet still on screen
                _PUTIMAGE (Bullets(b).x - 16, Bullets(b).y - 16), Sprite(FIRE, Bullets(b).Cell) ' draw bullet
                Bullets(b).Cell = Bullets(b).Cell + 1 '                                           increment animation cell
                IF Bullets(b).Cell = 5 THEN Bullets(b).Cell = 1 '                                 reset animation cell when needed
            END IF
        END IF
    LOOP UNTIL b = UBOUND(Bullets) '                                                              leave when array scanned

END SUB
' _____________________________________________________________________________________________________________________________________________
'/                                                                                                                                             \
SUB FIRE_BULLET () '                                                                                                               FIRE_BULLET |
    ' _________________________________________________________________________________________________________________________________________|____
    '/                                                                                                                                              \
    '| Fires a bullet.                                                                                                                              |
    '\______________________________________________________________________________________________________________________________________________/

    SHARED Mario AS TYPE_MARIO '      need access to mario properties
    SHARED Bullets() AS TYPE_BULLET ' need access to bullet array
    SHARED BulletTimer AS INTEGER '   need access to bullet timer
    SHARED SNDFire AS LONG '          need access to bullet sound
    DIM b AS INTEGER '                bullet counter

    IF BulletTimer THEN EXIT SUB '                           leave if not time to fire bullet
    BulletTimer = 10 '                                       must wait 10 frames between bullets (3 rounds per second)
    b = 0 '                                                  reset bullet counter
    DO '                                                     loop through bullet array
        b = b + 1 '                                          increment bullet counter
    LOOP UNTIL Bullets(b).InUse = 0 OR b = UBOUND(Bullets) ' leave when array scanned
    IF Bullets(b).InUse THEN '                               if this index in use then all indexes used
        b = b + 1 '                                          increment bullet counter
        REDIM _PRESERVE Bullets(b) AS TYPE_BULLET '          increase size of array
    END IF
    Bullets(b).InUse = -1 '                                  mark this index in use
    _SNDPLAY SNDFire '                                       play bullet sound
    IF Mario.Facing = 1 THEN '                               mario facing right?
        Bullets(b).x = Mario.Rect.x2 '                       yes, bullet comes from right side of mario
    ELSE '                                                   no, mario facing left
        Bullets(b).x = Mario.Rect.x1 '                       bullet comes from left side of mario
    END IF
    Bullets(b).y = Mario.Rect.y1 + 31 '                      bullet comes from vertical center of mario
    Bullets(b).Direction = Mario.Facing '                    remember bullet direction
    Bullets(b).Cell = 1 '                                    animation frame 1

END SUB
' _____________________________________________________________________________________________________________________________________________
'/                                                                                                                                             \
FUNCTION COLLISION (R1 AS TYPE_RECTANGLE, R2 AS TYPE_RECTANGLE) '                                                                    COLLISION |
    ' _________________________________________________________________________________________________________________________________________|____
    '/                                                                                                                                              \
    '| Detects and reports a collision between two rectangular areas provided, -1 (TRUE) if in collision, 0 (FALSE) otherwise.                      |
    '|                                                                                                                                              |
    '| R1 - rectangle 1                                                                                                                             |
    '| R2 - rectangle 2                                                                                                                             |
    '\______________________________________________________________________________________________________________________________________________/

    COLLISION = 0 '                      assume no collision
    IF R1.x2 >= R2.x1 THEN '             does R1 overlap R2?
        IF R1.x1 <= R2.x2 THEN
            IF R1.y2 >= R2.y1 THEN
                IF R1.y1 <= R2.y2 THEN
                    COLLISION = -1 '     yes, report rectangles in state of collision
                END IF
            END IF
        END IF
    END IF

END FUNCTION
' _____________________________________________________________________________________________________________________________________________
'/                                                                                                                                             \
SUB SET_DEFAULT_BUTTONS () '                                                                                               SET_DEFAULT_BUTTONS |
    ' _________________________________________________________________________________________________________________________________________|____
    '/                                                                                                                                              \
    '| Sets the default buttons. Will use keyboard and/or joystick 1 if they are detected.                                                          |
    '\______________________________________________________________________________________________________________________________________________/

    SHARED Button AS TYPE_BUTTON ' need access to button array
    STATIC Configured AS INTEGER ' -1 when buttons previously created and configured

    '+-----------------------------------+
    '| Clear previous button assignments |
    '+-----------------------------------+

    IF Configured THEN '                                             have buttons been created and configured
        __REMOVE_BUTTON Button.UP, 0 '                               yes, clear all previous button assignments
        __REMOVE_BUTTON Button.DOWN, 0
        __REMOVE_BUTTON Button.LEFT, 0
        __REMOVE_BUTTON Button.RIGHT, 0
        __REMOVE_BUTTON Button.FIRE, 0
        __REMOVE_BUTTON Button.RUN, 0
        __REMOVE_BUTTON Button.JUMP, 0
        __REMOVE_BUTTON Button.CROUCH, 0
    END IF

    '+-------------------------------------------------------+
    '| Configure keyboard keys as buttons if keyboard exists |
    '+-------------------------------------------------------+

    IF __KEYBOARD_EXISTS THEN '                                      does keyboard exist?
        __ASSIGN_BUTTON Button.UP, __KEYBOARDCID, CLKEY_W '          UP       = W KEY               [SLOT1]
        __ASSIGN_BUTTON Button.DOWN, __KEYBOARDCID, CLKEY_S '        DOWN     = S KEY               [SLOT1]
        __ASSIGN_BUTTON Button.LEFT, __KEYBOARDCID, CLKEY_A '        LEFT     = A KEY               [SLOT1]
        __ASSIGN_BUTTON Button.RIGHT, __KEYBOARDCID, CLKEY_D '       RIGHT    = D KEY               [SLOT1]
        __ASSIGN_BUTTON Button.FIRE, __KEYBOARDCID, CLKEY_RCTRL '    FIRE     = RIGHT CONTROL KEY   [SLOT1]
        __ASSIGN_BUTTON Button.RUN, __KEYBOARDCID, CLKEY_RSHIFT '    RUN      = RIGHT SHIFT KEY     [SLOT1]
        __ASSIGN_BUTTON Button.JUMP, __KEYBOARDCID, CLKEY_SPACEBAR ' JUMP     = SPACE BAR KEY       [SLOT1]
        __ASSIGN_BUTTON Button.CROUCH, __KEYBOARDCID, CLKEY_C '      CROUCH   = C KEY               [SLOT1]

        '+----------------------+
        '| Optional assignments |
        '+----------------------+

        '__ASSIGN_BUTTON Button.UP, __KEYBOARDCID, CLKEY_UP '         UP       = UP ARROW KEY        [SLOT2]
        '__ASSIGN_BUTTON Button.DOWN, __KEYBOARDCID, CLKEY_DOWN '     DOWN     = DOWN ARROW KEY      [SLOT2]
        '__ASSIGN_BUTTON Button.LEFT, __KEYBOARDCID, CLKEY_LEFT '     LEFT     = LEFT ARROW KEY      [SLOT2]
        '__ASSIGN_BUTTON Button.RIGHT, __KEYBOARDCID, CLKEY_RIGHT '   RIGHT    = RIGHT ARROW KEY     [SLOT2]
        '__ASSIGN_BUTTON Button.FIRE, __KEYBOARDCID, CLKEY_LCTRL '    FIRE     = LEFT CONTROL KEY    [SLOT2]


    END IF

    '+---------------------------------------------------------------+
    '| Configure joystick 1 axes and buttons if it exists (optional) |
    '+---------------------------------------------------------------+

    'IF __JOYPAD_EXISTS(1) THEN '                                                                   does joystick 1 exist?
    '    __ASSIGN_AXIS Button.UP, __JOYPAD1CID, -2 '                                                UP       = JOYSTICK 1 AXIS 2   [SLOT3 or 1]
    '    __ASSIGN_AXIS Button.DOWN, __JOYPAD1CID, 2 '                                               DOWN     = JOYSTICK 1 AXIS 2   [SLOT3 or 1]
    '    __ASSIGN_AXIS Button.LEFT, __JOYPAD1CID, -1 '                                              LEFT     = JOYSTICK 1 AXIS 1   [SLOT3 or 1]
    '    __ASSIGN_AXIS Button.RIGHT, __JOYPAD1CID, 1 '                                              RIGHT    = JOYSTICK 1 AXIS 1   [SLOT3 or 1]
    '    __ASSIGN_BUTTON Button.FIRE, __JOYPAD1CID, 1 '                                             FIRE     = JOYSTICK 1 BUTTON 1 [SLOT3 or 1]
    '    IF __BUTTON_TOTAL(__JOYPAD1CID) > 1 THEN __ASSIGN_BUTTON Button.JUMP, __JOYPAD1CID, 2 '    JUMP     = JOYSTICK 1 BUTTON 2 [SLOT2 or 1]
    '    IF __BUTTON_TOTAL(__JOYPAD1CID) > 2 THEN __ASSIGN_BUTTON Button.RUN, __JOYPAD1CID, 3 '     RUN      = JOYSTICK 1 BUTTON 4 [SLOT2 or 1]
    '    IF __BUTTON_TOTAL(__JOYPAD1CID) >= 3 THEN __ASSIGN_BUTTON Button.CROUCH, __JOYPAD1CID, 4 ' CROUCH   = JOYSTICK 1 BUTTON 5 [SLOT2 or 1]
    'END IF
    Configured = -1 '                                                buttons have been created and configured

END SUB
' _____________________________________________________________________________________________________________________________________________
'/                                                                                                                                             \
SUB LOAD_ASSETS () '                                                                                                               LOAD_ASSETS |
    ' _________________________________________________________________________________________________________________________________________|____
    '/                                                                                                                                              \
    '| Load the demo's graphics and sounds                                                                                                          |
    '\______________________________________________________________________________________________________________________________________________/

    SHARED Sprite() AS LONG '   need access to sprite images
    SHARED WorkScreen AS LONG ' need access to work screen
    SHARED BackGround AS LONG ' need access to background image
    SHARED MysteryBox AS LONG ' need access to mystery box image
    SHARED SNDBump AS LONG '    need access to sounds
    SHARED SNDCoin AS LONG
    SHARED SNDConf AS LONG
    SHARED SNDFire AS LONG
    SHARED SNDJump AS LONG
    SHARED SNDSong AS LONG
    DIM Sheet AS LONG '         demo sprite sheet
    DIM Ground AS LONG '        ground square image
    DIM i AS INTEGER '          generic counter

    SNDBump = _SNDOPEN("CB_Bump.ogg") '                                                      load sounds
    SNDCoin = _SNDOPEN("CB_Coin.ogg")
    SNDFire = _SNDOPEN("CB_Fire.ogg")
    SNDJump = _SNDOPEN("CB_Jump.ogg")
    SNDSong = _SNDOPEN("CB_Song.ogg")
    SNDConf = _SNDOPEN("CB_Conf.ogg")
    Sheet = _LOADIMAGE("CBDemo.PNG", 32) '                                                   sprite sheet image
    WorkScreen = _NEWIMAGE(320, 240, 32) '                                                   work screen image
    BackGround = _NEWIMAGE(320, 240, 32) '                                                   background image
    Sprite(STAND, 1) = _NEWIMAGE(32, 64, 32) '                                               standing image
    Sprite(CROUCH, 1) = _NEWIMAGE(32, 64, 32) '                                              crouching image
    Sprite(RUNSTART, 1) = _NEWIMAGE(32, 64, 32) '                                            ready to run image
    Sprite(JUMP, 1) = _NEWIMAGE(32, 64, 32) '                                                jumping image
    MysteryBox = _NEWIMAGE(32, 32, 32) '                                                     mystery box image
    Sprite(COIN, 1) = _NEWIMAGE(32, 32, 32) '                                                coin image
    Ground = _NEWIMAGE(32, 32, 32) '                                                         ground image
    _PUTIMAGE , Sheet, MysteryBox, (128, 64)-(159, 95) '                                     get mystery box image from sprite sheet
    _PUTIMAGE , Sheet, Sprite(COIN, 1), (160, 64)-(191, 95) '                                get coin image from sprite sheet
    _PUTIMAGE , Sheet, Ground, (192, 64)-(223, 95) '                                         get ground image from sprite sheet
    _PUTIMAGE , Sheet, Sprite(STAND, 1), (0, 0)-(31, 64) '                                   get standing image from sprite sheet
    _PUTIMAGE , Sheet, Sprite(CROUCH, 1), (32, 0)-(63, 63) '                                 get crouching image from sprite sheet
    _PUTIMAGE , Sheet, Sprite(RUNSTART, 1), (64, 0)-(95, 63) '                               get ready to run image from sprite sheet
    _PUTIMAGE , Sheet, Sprite(JUMP, 1), (288, 0)-(319, 63) '                                 get jumping image from sprite sheet
    FOR i = 1 TO 6 '                                                                         cycle through 6 animation cells
        Sprite(WALK, i) = _NEWIMAGE(32, 64, 32) '                                            walking animation image cell
        _PUTIMAGE , Sheet, Sprite(WALK, i), (64 + i * 32, 0)-(95 + i * 32, 63) '             get walking animation cell from sprite sheet
        IF i < 5 THEN '                                                                      cycle through 4 animation cells
            Sprite(FIRE, i) = _NEWIMAGE(32, 32, 32) '                                        bullet animation image cell
            _PUTIMAGE , Sheet, Sprite(FIRE, i), ((i - 1) * 32, 64)-((i - 1) * 32 + 31, 95) ' get bullet animation cell from sprite sheet
        END IF
    NEXT i
    _DEST BackGround '                                                                       draw on background image
    CLS , _RGB32(0, 255, 0) '                                                                clear background image in green
    FOR i = 0 TO 288 STEP 32 '                                                               cycle through the floor area
        _PUTIMAGE (i, 176), Ground '                                                         draw the floor ground blocks
        _PUTIMAGE (i, 208), Ground
    NEXT i
    _FREEIMAGE Ground '                                                                      ground image no longer needed
    _FREEIMAGE Sheet '                                                                       sprite sheet no longer needed
    _DEST 0 '                                                                                return to main screen

END SUB
' _____________________________________________________________________________________________________________________________________________
'/                                                                                                                                             \
SUB INITIALIZE_VARIABLES () '                                                                                             INITIALIZE_VARIABLES |
    ' _________________________________________________________________________________________________________________________________________|____
    '/                                                                                                                                              \
    '| Initialize all demo variables                                                                                                                |
    '\______________________________________________________________________________________________________________________________________________/

    SHARED Button AS TYPE_BUTTON ' need access to buttons
    SHARED Mario AS TYPE_MARIO '   need access to mario properties
    SHARED Box AS TYPE_RECTANGLE ' need access to box coordinates

    Mario.FPS = 5 '                                      mario animation speed
    Mario.Direction = 1 '                                mario travel direction (right)
    Mario.Facing = 1 '                                   mario facing direction (right)
    Mario.Floor = 176 '                                  floor that mario can't go beyond
    Mario.x = 159 '                                      x coordinate of mario (bottom center of sprite)
    Mario.y = Mario.Floor '                              y coordinate of mario (bottom center of sprite)
    Mario.Action = STAND '                               mario is currently standing
    Mario.Frame = 1 '                                    first animation frame
    Mario.Rect.x1 = Mario.x - 15 '                       calculate mario sprite rectangular coordinates
    Mario.Rect.y1 = Mario.y - 63
    Mario.Rect.x2 = Mario.Rect.x1 + 31
    Mario.Rect.y2 = Mario.Rect.y1 + 63
    Box.x1 = 144 '                                       mystery box rectangular coordinates
    Box.y1 = 48
    Box.x2 = Box.x1 + 31
    Box.y2 = Box.y1 + 31
    __ASSIGN_BUTTON Button.F1, __KEYBOARDCID, CLKEY_F1 ' F1 key
    __ASSIGN_BUTTON Button.F2, __KEYBOARDCID, CLKEY_F2 ' F2 key
    __ASSIGN_BUTTON Button.F3, __KEYBOARDCID, CLKEY_F3 ' F3 key
END SUB

'$INCLUDE:'CONTROLLER.BM'



RE: Controller Library - TempodiBasic - 05-02-2023

Hi Terry
I stress out " very good controller library!"

about the issue of detecting at will the controllers it stands up on _DEVICES keyword that is in development, so also using your fantastic library I stuck in the same issue.
Thank you for sharing this masterpiece that will grow up togeter QB64pe.

Screenshot of results of my attempt using your library

[Image: immagine-2023-05-02-114448700.png]

and here correlate QB64 code
Code: (Select All)
'$INCLUDE:'CONTROLLER.BI'
ReDim Shared Ax(1 To 1) As Integer, Bx(1 To 1) As Integer, Wx(1 To 1) As Integer
ReDim Shared Axm As Integer, Bxm As Integer, Wxm As Integer
Dim Kh As Integer
__INITIALIZE_CONTROLLERS '                                 initialize controller library


Cls
Print " Press ESC to quit and Enter to detect joystick"
Print " JoyStick   Axis         Buttons       Wheels"
__IDENTIFY_CONTROLLERS 'Print IsJoystick%, Axm, Bxm, Wxm
View Print 4 To 24
Kh = 0
While Kh <> 27
    Kh = _KeyHit
    If (Kh) = 13 Then __IDENTIFY_CONTROLLERS: Print __CONTROLLER_NAME$(3), __BUTTON_TOTAL(3), __AXIS_TOTAL(3) 'Print IsJoystick%, Axm, Bxm, Wxm
    Locate 24, 1: Print Kh;
    _Limit 30
Wend
End

'*****************************************************************
'             JOYSTICK DETECTION
'*****************************************************************
Function IsJoystick% ()
    Dim HMD%: HMD% = HowManyDevice%

    If HMD% = 0 Then
        Print " No input devices!": End
    Else
        Locate , 1: Print HMD%
    End If

    If HMD% = 3 Then
        Locate , 4: Print "Joystick detected"
        IsJoystick% = -1
        Axm = _LastAxis(3)
        Bxm = _LastButton(3)
        Wxm = _LastWheel(3)
    Else
        ' all cases in which HMD% <>3
        Locate , 10: Print " NO Joystick!"
        IsJoystick% = 0
        Axm = 0
        Bxm = 0
        Wxm = 0
    End If
    Print HMD%, Axm, Bxm, Wxm
End Function

Function HowManyDevice%
    HowManyDevice% = 0 ' error value
    HowManyDevice% = _Devices ' value detected
End Function
'*****************************************************************
'        END Subs and Function for JOYSTICK DETECTION
'*****************************************************************

'$INCLUDE:'CONTROLLER.BM'

Moreover I agree with you for thanking SMcNeill and all the others of the QB64pe development team.


RE: Controller Library - TempodiBasic - 05-13-2023

Hi Terry
thanks for all your good efforts to show the paths to different purpuses.

Your demo for remapping user input for joystick is very nice.
It is very good. 
No plugin/ unplug  allowed after program runs, but this is ok, which player does change the controller during the play.

a music gift
GOSSIP by Maneskin


RE: Controller Library - TerryRitchie - 05-13-2023

(05-13-2023, 11:26 AM)TempodiBasic Wrote: Hi Terry
thanks for all your good efforts to show the paths to different purpuses.

Your demo for remapping user input for joystick is very nice.
It is very good. 
No plugin/ unplug  allowed after program runs, but this is ok, which player does change the controller during the play.

a music gift
GOSSIP by Maneskin

I'm working on adding the ability to notice when new controllers are plugged in and current ones have been disconnected.