Welcome, Guest
You have to register before you can post on our site.

Username/Email:
  

Password
  





Search Forums

(Advanced Search)

Forum Statistics
» Members: 308
» Latest member: Donaldvem
» Forum threads: 1,741
» Forum posts: 17,901

Full Statistics

Latest Threads
The QB64 IDE shell
Forum: Utilities
Last Post: JasonPag
09-16-2024, 05:37 PM
» Replies: 9
» Views: 764
Importance regarding Ches...
Forum: Utilities
Last Post: JasonPag
09-01-2024, 06:34 PM
» Replies: 0
» Views: 31
Chess and Analysis and En...
Forum: Utilities
Last Post: JasonPag
08-28-2024, 02:37 PM
» Replies: 0
» Views: 32
DAY 009:_PutImage
Forum: Keyword of the Day!
Last Post: grymmjack
09-02-2023, 02:57 PM
» Replies: 54
» Views: 2,034
Fall Banner Contest?
Forum: Site Suggestions
Last Post: grymmjack
08-31-2023, 11:50 PM
» Replies: 36
» Views: 1,262
ColorPicker - Function th...
Forum: Dav
Last Post: Dav
08-31-2023, 11:04 PM
» Replies: 3
» Views: 316
Goals(1) = New Tile()
Forum: Works in Progress
Last Post: RhoSigma
08-31-2023, 09:45 PM
» Replies: 3
» Views: 127
micro(A)v11
Forum: QBJS, BAM, and Other BASICs
Last Post: bplus
08-31-2023, 09:14 PM
» Replies: 90
» Views: 3,589
Updating The Single Most ...
Forum: QBJS, BAM, and Other BASICs
Last Post: bplus
08-31-2023, 09:13 PM
» Replies: 7
» Views: 254
QBJS Image Question
Forum: QBJS, BAM, and Other BASICs
Last Post: bplus
08-31-2023, 05:49 PM
» Replies: 5
» Views: 155

 
  Multi-Spacewar! WIP but playable!!!!!!
Posted by: madscijr - 11-04-2022, 03:54 PM - Forum: Works in Progress - No Replies

I have been wanting to see this game since oh, 1985 or so, give it a try and tell me how you like it!

This is still very much a WIP - the control mapping is hardcoded for now, but can be changed beginning at line 2043. Player 1 is controlled by the cursor keys and right CTRL. 

I plan on making the controls user-configurable, including support for game controllers. I also will add sounds, display player scores/life/etc. along the edges of the screen, add a sun/gravity, and a whole bunch of other menu-driven user configurable options (eventually to be a kind of Spacewar! / Asteroids / Gravitar / Lunar Lander construction set). 

But this is the first playable version, so here you go. 

PS I forgot to include a screenshot and am not at my PC anymore, so maybe next version. 

Here is the current keyboard mapping:

Code: (Select All)
        m_arrShip(1).Left_KeyCode = KeyCode_Left%
        m_arrShip(1).Right_KeyCode = KeyCode_Right%
        m_arrShip(1).Up_KeyCode = KeyCode_Up%
        m_arrShip(1).Down_KeyCode = KeyCode_Down%
        m_arrShip(1).Shoot_KeyCode = KeyCode_CtrlRight%
        m_arrShip(1).Cheat_KeyCode = KeyCode_Keypad0Ins%
        
        m_arrShip(2).Left_KeyCode = KeyCode_Keypad7Home%
        m_arrShip(2).Right_KeyCode = KeyCode_Keypad8Up%
        m_arrShip(2).Up_KeyCode = KeyCode_Keypad9PgUp%
        m_arrShip(2).Down_KeyCode = KeyCode_Keypad6Right%
        m_arrShip(2).Shoot_KeyCode = KeyCode_Keypad5%
        m_arrShip(2).Cheat_KeyCode = KeyCode_Keypad4Left%
        
        m_arrShip(3).Left_KeyCode = KeyCode_Keypad1End%
        m_arrShip(3).Right_KeyCode = KeyCode_Keypad2Down%
        m_arrShip(3).Up_KeyCode = KeyCode_Keypad3PgDn%
        m_arrShip(3).Down_KeyCode = KeyCode_KeypadPeriodDel%
        m_arrShip(3).Shoot_KeyCode = KeyCode_KeypadEnter%
        m_arrShip(3).Cheat_KeyCode = KeyCode_KeypadPlus%
        
        m_arrShip(4).Left_KeyCode = KeyCode_1%
        m_arrShip(4).Right_KeyCode = KeyCode_2%
        m_arrShip(4).Up_KeyCode = KeyCode_3%
        m_arrShip(4).Down_KeyCode = KeyCode_4%
        m_arrShip(4).Shoot_KeyCode = KeyCode_5%
        m_arrShip(4).Cheat_KeyCode = KeyCode_Tilde%
        
        m_arrShip(5).Left_KeyCode = KeyCode_6%
        m_arrShip(5).Right_KeyCode = KeyCode_7%
        m_arrShip(5).Up_KeyCode = KeyCode_8%
        m_arrShip(5).Down_KeyCode = KeyCode_9%
        m_arrShip(5).Shoot_KeyCode = KeyCode_0%
        m_arrShip(5).Cheat_KeyCode = KeyCode_Minus%
        
        m_arrShip(6).Left_KeyCode = KeyCode_BracketLeft%
        m_arrShip(6).Right_KeyCode = KeyCode_BracketRight%
        m_arrShip(6).Up_KeyCode = KeyCode_BkSp%
        m_arrShip(6).Down_KeyCode = KeyCode_Enter%
        m_arrShip(6).Shoot_KeyCode = KeyCode_Backslash%
        m_arrShip(6).Cheat_KeyCode = KeyCode_Equal%
        
        m_arrShip(7).Left_KeyCode = KeyCode_Ins%
        m_arrShip(7).Right_KeyCode = KeyCode_Home%
        m_arrShip(7).Up_KeyCode = KeyCode_PgDn%
        m_arrShip(7).Down_KeyCode = KeyCode_PgUp%
        m_arrShip(7).Shoot_KeyCode = KeyCode_End%
        m_arrShip(7).Cheat_KeyCode = KeyCode_Del%
        
        m_arrShip(8).Left_KeyCode = KeyCode_Q%
        m_arrShip(8).Right_KeyCode = KeyCode_W%
        m_arrShip(8).Up_KeyCode = KeyCode_E%
        m_arrShip(8).Down_KeyCode = KeyCode_R%
        m_arrShip(8).Shoot_KeyCode = KeyCode_T%
        m_arrShip(8).Cheat_KeyCode = KeyCode_KeypadSlash%
        
        m_arrShip(9).Left_KeyCode = KeyCode_Y%
        m_arrShip(9).Right_KeyCode = KeyCode_U%
        m_arrShip(9).Up_KeyCode = KeyCode_I%
        m_arrShip(9).Down_KeyCode = KeyCode_O%
        m_arrShip(9).Shoot_KeyCode = KeyCode_P%
        m_arrShip(9).Cheat_KeyCode = KeyCode_KeypadMultiply%
        
        m_arrShip(10).Left_KeyCode = KeyCode_A%
        m_arrShip(10).Right_KeyCode = KeyCode_S%
        m_arrShip(10).Up_KeyCode = KeyCode_D%
        m_arrShip(10).Down_KeyCode = KeyCode_F%
        m_arrShip(10).Shoot_KeyCode = KeyCode_G%
        m_arrShip(10).Cheat_KeyCode = KeyCode_KeypadMinus%
        
        m_arrShip(11).Left_KeyCode = KeyCode_Z%
        m_arrShip(11).Right_KeyCode = KeyCode_X%
        m_arrShip(11).Up_KeyCode = KeyCode_C%
        m_arrShip(11).Down_KeyCode = KeyCode_V%
        m_arrShip(11).Shoot_KeyCode = KeyCode_B%
        m_arrShip(11).Cheat_KeyCode = KeyCode_F12%
        
        m_arrShip(12).Left_KeyCode = KeyCode_N%
        m_arrShip(12).Right_KeyCode = KeyCode_M%
        m_arrShip(12).Up_KeyCode = KeyCode_Comma%
        m_arrShip(12).Down_KeyCode = KeyCode_Period%
        m_arrShip(12).Shoot_KeyCode = KeyCode_Slash%
        m_arrShip(12).Cheat_KeyCode = KeyCode_Menu%
        
        m_arrShip(13).Left_KeyCode = KeyCode_H%
        m_arrShip(13).Right_KeyCode = KeyCode_J%
        m_arrShip(13).Up_KeyCode = KeyCode_K%
        m_arrShip(13).Down_KeyCode = KeyCode_L%
        m_arrShip(13).Shoot_KeyCode = KeyCode_Semicolon%
        m_arrShip(13).Cheat_KeyCode = KeyCode_Apostrophe%
        
        m_arrShip(14).Left_KeyCode = KeyCode_F1%
        m_arrShip(14).Right_KeyCode = KeyCode_F2%
        m_arrShip(14).Up_KeyCode = KeyCode_F3%
        m_arrShip(14).Down_KeyCode = KeyCode_F4%
        m_arrShip(14).Shoot_KeyCode = KeyCode_F5%
        m_arrShip(14).Cheat_KeyCode = KeyCode_ScrollLock%
        
        m_arrShip(15).Left_KeyCode = KeyCode_F6%
        m_arrShip(15).Right_KeyCode = KeyCode_F7%
        m_arrShip(15).Up_KeyCode = KeyCode_F8%
        m_arrShip(15).Down_KeyCode = KeyCode_F9%
        m_arrShip(15).Shoot_KeyCode = KeyCode_F11%
        m_arrShip(15).Cheat_KeyCode = KeyCode_NumLock%
        
        m_arrShip(16).Left_KeyCode = KeyCode_Spacebar%
        m_arrShip(16).Right_KeyCode = KeyCode_ShiftRight%
        m_arrShip(16).Up_KeyCode = KeyCode_ShiftLeft%
        m_arrShip(16).Down_KeyCode = KeyCode_CtrlLeft%
        m_arrShip(16).Shoot_KeyCode = KeyCode_Tab%
        m_arrShip(16).Cheat_KeyCode = KeyCode_CapsLock%


And here is the full program:

Code: (Select All)
_Title "Fast Zap 'Em : Multispacewar! by Softintheheadware v0.44"

' Borrowed code:
' * Original VB6 code for "Collisions" by Tassadar, found on PlanetSourceCode.com in 2001.
' * Graphics objects and format from Widescreen Asteroids by Terry Ritchie.

' DATE         WHO-DONE-IT   DID-WHAT
' 2001-06-10   Tassadar      created "Collisions" program
' 2022-11-01   madscijr      Converted "Collisions" to QB64, added some tweaks
' 2022-11-04   madscijr      Version 0.44 is now playable with up to 16 players

' DONE:
' * get it working in QB64 + tweak various calculations
' * random enemy radius (wider range with each level)
' * added variables for bullet lifespan, bullet radius
' * bullets can wrap around screen
' * bullet-bullet collisions
' * bonus shields at end of round
' * are you lookin' at me??
' * track x/y velocity differently (movement is jerky)
' * vector engine: define vector objects line by line and draw to screen
' * vector engine: translate rotation angle into dx,dy to move in direction of angle
' * local multiplayer Spacewar! (upto 16 players)
' * finish collisions
'   - player hit player
'   - player shoot player
'   - enemy shoot enemy

' TODO:
' * fix player placement
' * sun and gravity
' * explosions and stuff
' * sound effects
' * cleanup/fix menu (breaks if we remove test items)
' * show player # or name on or near ship
' * fix jerky ship movement (ships should move & respond like Asteroids)
' * improve collision detection accuracy
' * limited fuel/ammo
' * option: fire button auto-repeat
' (lots more plans for phase 2 and beyond)

' ORIGINAL CREDITS:
'********************************************************************************
'*            This is My Program, I made it to show some trig stuff             *
'* and got a little carried away, but it shows how to do trigonometry anyways   *
'*                                                                              *
'*    You are permitted to do whatever you want with this code                  *
'*        eg. Feel free to modify this code, steal it, etc.                     *
'*               I don't really give a crap!                                    *
'*                                                                              *
'* Programmed by Tassadar                                                       *
'********************************************************************************

' USEFUL CONSTANTS
Const FALSE = 0
Const TRUE = Not FALSE
'Const Pi = 3.14159 ' Pi , bit obvious

Const SHIP_RADIUS = 10 ' What is the radius of the players ship
Const BULLET_RADIUS = 2 ' What is the radius of the bullets
Const SHIP_ACCEL = 0.05 ' 0.1 ' how fast does it accelerate
Const MAX_SPEED = 12 ' 6 ' what is the ships max speed
Const SHOOT_DELAY = 200 ' Delay between shots for the ship
Const BULLET_SPEED = 9 ' 6 ' Bullet speed - Ship speed + bullet speed = overall bulletspeed
Const BULLET_LIFESPAN = 1 ' # seconds bullet is alive
Const BULLETS_STOP_BULLETS = TRUE

Const TURN_SPEED = 72 ' 36=faster 18=superfast

Const MIN_ENEMY_RADIUS = 6 ' What is the initial minimum radius of the enemy ships
Const MAX_ENEMY_RADIUS = 99 ' What is the initial maximum radius of the enemy ships

Const BULLET_DAMAGE = 5
Const WRAP_BULLET = TRUE
Const BONUS_SHIELDS = 50

' UDT TO HOLD MENU OPTIONS
Type MenuType
    Choice As String
    Info As String
End Type ' MenuType

Type ShipType
    name As String
    score As Integer
    level As Integer
    shields As Integer ' how much shields does the ship have
    fuel As Integer
    ammo As Integer
   
    radius As Integer
   
    xPos As Integer ' X co-ordinate of the ship
    yPos As Integer ' Y co-ordinate of the ship
    dx As Single ' x multiplier
    dy As Single ' y multiplier
    vx As Single ' x velocity
    vy As Single ' y velocity
   
    heading As Single ' which direction is the ship heading
    facing As Single ' which direction is the ship facing
    speed As Single ' how fast is the ship going
    ShootTime As Long
    ShootCount As Long
   
    Left_KeyCode As Integer ' stores key code for Left
    Right_KeyCode As Integer ' stores key code for Right
    Up_KeyCode As Integer ' stores key code for Up
    Down_KeyCode As Integer ' stores key code for Down
    Shoot_KeyCode As Integer ' stores key code for Shoot
    Cheat_KeyCode As Integer ' stores key code for Cheat
   
    Left_IsPressed As Integer ' Is the LeftKey depressed
    Right_IsPressed As Integer ' Is the RightKey depressed
    Up_IsPressed As Integer ' Is the UpKey depressed
    Down_IsPressed As Integer ' Is the DownKey depressed
    Shoot_IsPressed As Integer ' Is the ShootKey depressed
    Cheat_IsPressed As Integer ' Is the cheat key depressed
   
    BodyColor As _Unsigned Long
    EngineColor As _Unsigned Long
    FlameColor As _Unsigned Long
End Type ' ShipType

Type EnemyType
    xPos As Integer ' X position of this enemy
    yPos As Integer ' Y position of this enemy
    life As Integer ' How much life does this enemy have
    alive As Integer ' Is this enemy alive
    radius As Integer ' size of enemy ship
End Type ' EnemyType

Type BulletType
    xPos As Integer ' X co-ordinate of this bullet
    yPos As Integer ' Y co-ordinate of this bullet
    heading As Single ' Direction this bullet is heading
    speed As Single ' Speed of this bullet
    alive As Integer ' Is this bullet alive
    kind As String ' What type of bullet is this (Players or enemies)
    owner As Integer ' player # who fired shot (if kind = player)
    lifespan As Long
    lifetime As Long
End Type ' BulletType

' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
' BEGIN SIMPLE VECTOR ENGINE TYPES
' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
' HOLDS POSITION AND VELOCITY OF ALL OBJECTS
Type ObjectType
    x As Integer
    y As Integer
    dx As Integer
    dy As Integer
    cx As Integer
    cy As Integer
    IsEnabled As Integer
End Type ' ObjectType

' HOLDS DEFINITION OF ALL OBJECTS
Type CoordType
    x1 As Integer
    y1 As Integer
    x2 As Integer
    y2 As Integer
    color As _Unsigned Long
    IsLast As Integer
End Type ' CoordType

' HOLDS COORDINATES FOR TEST ROTATION
Type PointsType
    x As Integer
    y As Integer
    color As _Unsigned Long
    angle As Integer
    rx As Integer
    ry As Integer
    dx As Integer
    dy As Integer
End Type ' PointsType
' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
' END SIMPLE VECTOR ENGINE TYPES
' ----------------------------------------------------------------------------------------------------------------------------------------------------------------

' ENABLE / DISABLE DEBUG CONSOLE
Dim Shared m_bDebug As Integer: m_bDebug = TRUE

' BASIC PROGRAM METADATA
Dim Shared m_ProgramPath$: m_ProgramPath$ = Left$(Command$(0), _InStrRev(Command$(0), "\"))
Dim Shared m_ProgramName$: m_ProgramName$ = Mid$(Command$(0), _InStrRev(Command$(0), "\") + 1)
Dim Shared m_VersionInfo$: m_VersionInfo$ = "1.00"

' FOR MENU
ReDim Shared m_arrMenu(-1) As MenuType

' OTHER USEFUL VARIABLES
Dim Shared PI As Single: PI = 4 * Atn(1)
Dim Shared ENGINE_RADIUS As Integer: ENGINE_RADIUS = SHIP_RADIUS * 0.6
Dim Shared FLAME_RADIUS As Integer: FLAME_RADIUS = SHIP_RADIUS * 2
Dim Shared m_arrDX(0 To 143) As Single
Dim Shared m_arrDY(0 To 143) As Single
Dim Shared iFPS As Integer: iFPS = 60 ' Delay between frames (frames per second)
Dim Shared iMinX As Integer: iMinX = 1
Dim Shared iMaxX As Integer: iMaxX = 1024
Dim Shared iMinY As Integer: iMinY = 1
Dim Shared iMaxY As Integer: iMaxY = 768

' GAME STATE
Dim Shared m_iPlayers As Integer ' How many players
Dim Shared m_bGameOver As Integer ' Is the game over
Dim Shared m_bAllDead As Integer ' Are all the enemies dead
Dim Shared m_iLevel As Integer ' Track the level
'Dim Shared m_iScore As Integer ' Keeps track of player score
Dim Shared m_iMinEnemyRadius As Integer ' current minimum enemy radius
Dim Shared m_iMaxEnemyRadius As Integer ' current maximum enemy radius

' INPUT VARIABLES
Dim Shared m_bEscKey As Integer

' GAME OBJECTS
ReDim Shared m_arrShip(-1) As ShipType ' A nice array of players
ReDim Shared m_arrEnemy(-1) As EnemyType ' A nice array of enemies
ReDim Shared m_arrBullet(-1) As BulletType ' A nice array of Bullets

' VECTOR OBJECT VARIABLES
ReDim Shared m_arrObject(1 To 8) As ObjectType
ReDim Shared m_arrLines(1 To 8, 1 To 32) As CoordType ' (object #, line segment #)

' ****************************************************************************************************************************************************************
' ACTIVATE DEBUGGING WINDOW
If m_bDebug = TRUE Then
    $Console
    _Delay 4
    _Console On
    _Echo "Started " + m_ProgramName$
    _Echo "Debugging on..."
End If
' ****************************************************************************************************************************************************************

' =============================================================================
' START THE MAIN ROUTINE
main

' =============================================================================
' FINISH
'Screen 0
'Print m_ProgramName$ + " finished."
'Sleep

' ****************************************************************************************************************************************************************
' DEACTIVATE DEBUGGING WINDOW
If m_bDebug = TRUE Then
    _Console Off
End If
' ****************************************************************************************************************************************************************

' FINISHED
System ' return control to the operating system

' ################################################################################################################################################################
' BEGIN ADD YOUR CUSTOM MENU ITEMS HERE #CUSTOMENU
' ################################################################################################################################################################

' /////////////////////////////////////////////////////////////////////////////

Sub InitializeGlobal
    ' *****************************************************************************
    ' *** BEGIN ADD YOUR MENU CHOICES HERE ****************************************

    AddNextMenuItem "game", "Play Multispacewar!"
    AddNextMenuItem "DrawVectorObjectTest1", "Test simple vector graphics engine."
    AddNextMenuItem "RotatePointsTest1", "Rotate vector object test."
    'AddNextMenuItem "Calculate_DX_DY_per_angle_TEST_2", "Calculate dx,dy per angle."
    'AddNextMenuItem "SinCosTest1", "Sin/Cos test ."
    'AddNextMenuItem "Choice F", "Collision detection."
    'AddNextMenuItem "ShowDegreesAndRadians", "Outputs to console if m_bDebug=TRUE."
   
   
    'AddNextMenuItem "Choice A", "Option A uses code 65 to do its thing."
    'AddNextMenuItem "Choice B", "Option B uses code 66 to do its thing."
    'AddNextMenuItem "Choice C", "Option C uses code 67 to do its thing."
    AddNextMenuItem "Choice D", "Option D uses code 68 to do its thing."
    AddNextMenuItem "Choice E", "Option E uses code 69 to do its thing."
    AddNextMenuItem "Choice F", "Option F uses code 70 to do its thing."
    AddNextMenuItem "Choice G", "Option G uses code 71 to do its thing."
    AddNextMenuItem "Choice H", "Option H uses code 72 to do its thing."
    AddNextMenuItem "Choice I", "Option I uses code 73 to do its thing."
    AddNextMenuItem "Choice J", "Option J uses code 74 to do its thing."
    AddNextMenuItem "Choice K", "Option K uses code 75 to do its thing."
    AddNextMenuItem "Choice L", "Option L uses code 76 to do its thing."
    AddNextMenuItem "Choice M", "Option M uses code 77 to do its thing."
    AddNextMenuItem "Choice N", "Option N uses code 78 to do its thing."
    AddNextMenuItem "Choice O", "Option O uses code 79 to do its thing."
    AddNextMenuItem "Choice P", "Option P uses code 80 to do its thing."
    AddNextMenuItem "Choice Q", "Option Q uses code 81 to do its thing."
    AddNextMenuItem "Choice R", "Option R uses code 82 to do its thing."
    AddNextMenuItem "Choice S", "Option S uses code 83 to do its thing."
    AddNextMenuItem "Choice T", "Option T uses code 84 to do its thing."
    AddNextMenuItem "Choice U", "Option U uses code 85 to do its thing."
    AddNextMenuItem "Choice V", "Option V uses code 86 to do its thing."
    AddNextMenuItem "Choice W", "Option W uses code 87 to do its thing."
    AddNextMenuItem "Choice X", "Option X uses code 88 to do its thing."
    AddNextMenuItem "Choice Y", "Option Y uses code 89 to do its thing."
    AddNextMenuItem "Choice Z", "Option Z uses code 90 to do its thing."
   
    ' *** END ADD YOUR MENU CHOICES HERE ******************************************
    ' *****************************************************************************
End Sub ' InitializeGlobal

' /////////////////////////////////////////////////////////////////////////////

Sub DoMenuItem (iMenuPos As Integer)
    Dim in$
    ClearKeyboard 3
    in$ = m_arrMenu(iMenuPos).Choice
    If in$ = "" Then ' (DO NOTHING)

        ' *****************************************************************************
        ' *** BEGIN ADD YOUR MENU CHOICES AND WHAT CODE RUNS HERE *********************
   
    ElseIf in$ = "game" Then game: ClearKeyboard 3
    ElseIf in$ = "DrawVectorObjectTest1" Then DrawVectorObjectTest1: ClearKeyboard 3
    ElseIf in$ = "RotatePointsTest1" Then RotatePointsTest1: ClearKeyboard 3
        'ElseIf in$ = "Calculate_DX_DY_per_angle_TEST_2C" Then TestRoutineChoiceC: ClearKeyboard 3
        'ElseIf in$ = "SinCosTest1" Then TestRoutineChoiceD: ClearKeyboard 3
        'ElseIf in$ = "ShowDegreesAndRadians" Then ShowDegreesAndRadians: ClearKeyboard 3
   
        'ElseIf in$ = "Choice A" Then TestRoutineChoiceA: ClearKeyboard 3
        'ElseIf in$ = "Choice B" Then TestRoutineChoiceB: ClearKeyboard 3
        'ElseIf in$ = "Choice C" Then TestRoutineChoiceC: ClearKeyboard 3
    ElseIf in$ = "Choice D" Then TestRoutineChoiceD: ClearKeyboard 3
    ElseIf in$ = "Choice E" Then TestRoutineChoiceE: ClearKeyboard 3
    ElseIf in$ = "Choice F" Then TestRoutineChoiceF: ClearKeyboard 3
    ElseIf in$ = "Choice G" Then TestRoutineChoiceG: ClearKeyboard 3
    ElseIf in$ = "Choice H" Then TestRoutineChoiceH: ClearKeyboard 3
    ElseIf in$ = "Choice I" Then TestRoutineChoiceI: ClearKeyboard 3
    ElseIf in$ = "Choice J" Then TestRoutineChoiceJ: ClearKeyboard 3
    ElseIf in$ = "Choice K" Then TestRoutineChoiceK: ClearKeyboard 3
    ElseIf in$ = "Choice L" Then TestRoutineChoiceL: ClearKeyboard 3
    ElseIf in$ = "Choice M" Then TestRoutineChoiceM: ClearKeyboard 3
    ElseIf in$ = "Choice N" Then TestRoutineChoiceN: ClearKeyboard 3
    ElseIf in$ = "Choice O" Then TestRoutineChoiceO: ClearKeyboard 3
    ElseIf in$ = "Choice P" Then TestRoutineChoiceP: ClearKeyboard 3
    ElseIf in$ = "Choice Q" Then TestRoutineChoiceQ: ClearKeyboard 3
    ElseIf in$ = "Choice R" Then TestRoutineChoiceR: ClearKeyboard 3
    ElseIf in$ = "Choice S" Then TestRoutineChoiceS: ClearKeyboard 3
    ElseIf in$ = "Choice T" Then TestRoutineChoiceT: ClearKeyboard 3
    ElseIf in$ = "Choice U" Then TestRoutineChoiceU: ClearKeyboard 3
    ElseIf in$ = "Choice V" Then TestRoutineChoiceV: ClearKeyboard 3
    ElseIf in$ = "Choice W" Then TestRoutineChoiceW: ClearKeyboard 3
    ElseIf in$ = "Choice X" Then TestRoutineChoiceX: ClearKeyboard 3
    ElseIf in$ = "Choice Y" Then TestRoutineChoiceY: ClearKeyboard 3
    ElseIf in$ = "Choice Z" Then TestRoutineChoiceZ: ClearKeyboard 3
   
        ' *** END ADD YOUR MENU CHOICES AND WHAT CODE RUNS HERE ***********************
        ' *****************************************************************************

    Else
        ' (DO NOTHING)
    End If
End Sub ' DoMenuItem

' /////////////////////////////////////////////////////////////////////////////

Sub InitializeGlobal1
    ' *****************************************************************************
    ' *** BEGIN ADD YOUR MENU CHOICES HERE ****************************************

    AddNextMenuItem "Choice A", "Option A uses code 65 to do its thing."
    AddNextMenuItem "Choice B", "Option B uses code 66 to do its thing."
    AddNextMenuItem "Choice C", "Option C uses code 67 to do its thing."
    AddNextMenuItem "Choice D", "Option D uses code 68 to do its thing."
    AddNextMenuItem "Choice E", "Option E uses code 69 to do its thing."
    AddNextMenuItem "Choice F", "Option F uses code 70 to do its thing."
    AddNextMenuItem "Choice G", "Option G uses code 71 to do its thing."
    AddNextMenuItem "Choice H", "Option H uses code 72 to do its thing."
    AddNextMenuItem "Choice I", "Option I uses code 73 to do its thing."
    AddNextMenuItem "Choice J", "Option J uses code 74 to do its thing."
    AddNextMenuItem "Choice K", "Option K uses code 75 to do its thing."
    AddNextMenuItem "Choice L", "Option L uses code 76 to do its thing."
    AddNextMenuItem "Choice M", "Option M uses code 77 to do its thing."
    AddNextMenuItem "Choice N", "Option N uses code 78 to do its thing."
    AddNextMenuItem "Choice O", "Option O uses code 79 to do its thing."
    AddNextMenuItem "Choice P", "Option P uses code 80 to do its thing."
    AddNextMenuItem "Choice Q", "Option Q uses code 81 to do its thing."
    AddNextMenuItem "Choice R", "Option R uses code 82 to do its thing."
    AddNextMenuItem "Choice S", "Option S uses code 83 to do its thing."
    AddNextMenuItem "Choice T", "Option T uses code 84 to do its thing."
    AddNextMenuItem "Choice U", "Option U uses code 85 to do its thing."
    AddNextMenuItem "Choice V", "Option V uses code 86 to do its thing."
    AddNextMenuItem "Choice W", "Option W uses code 87 to do its thing."
    AddNextMenuItem "Choice X", "Option X uses code 88 to do its thing."
    AddNextMenuItem "Choice Y", "Option Y uses code 89 to do its thing."
    AddNextMenuItem "Choice Z", "Option Z uses code 90 to do its thing."

    ' *** END ADD YOUR MENU CHOICES HERE ******************************************
    ' *****************************************************************************
End Sub ' InitializeGlobal

' /////////////////////////////////////////////////////////////////////////////

Sub DoMenuItem1 (iMenuPos As Integer)
    Dim in$
    ClearKeyboard 3
    in$ = m_arrMenu(iMenuPos).Choice
    If in$ = "" Then ' (DO NOTHING)

        ' *****************************************************************************
        ' *** BEGIN ADD YOUR MENU CHOICES AND WHAT CODE RUNS HERE *********************

    ElseIf in$ = "Choice A" Then TestRoutineChoiceA: ClearKeyboard 3
    ElseIf in$ = "Choice B" Then TestRoutineChoiceB: ClearKeyboard 3
    ElseIf in$ = "Choice C" Then TestRoutineChoiceC: ClearKeyboard 3
    ElseIf in$ = "Choice D" Then TestRoutineChoiceD: ClearKeyboard 3
    ElseIf in$ = "Choice E" Then TestRoutineChoiceE: ClearKeyboard 3
    ElseIf in$ = "Choice F" Then TestRoutineChoiceF: ClearKeyboard 3
    ElseIf in$ = "Choice G" Then TestRoutineChoiceG: ClearKeyboard 3
    ElseIf in$ = "Choice H" Then TestRoutineChoiceH: ClearKeyboard 3
    ElseIf in$ = "Choice I" Then TestRoutineChoiceI: ClearKeyboard 3
    ElseIf in$ = "Choice J" Then TestRoutineChoiceJ: ClearKeyboard 3
    ElseIf in$ = "Choice K" Then TestRoutineChoiceK: ClearKeyboard 3
    ElseIf in$ = "Choice L" Then TestRoutineChoiceL: ClearKeyboard 3
    ElseIf in$ = "Choice M" Then TestRoutineChoiceM: ClearKeyboard 3
    ElseIf in$ = "Choice N" Then TestRoutineChoiceN: ClearKeyboard 3
    ElseIf in$ = "Choice O" Then TestRoutineChoiceO: ClearKeyboard 3
    ElseIf in$ = "Choice P" Then TestRoutineChoiceP: ClearKeyboard 3
    ElseIf in$ = "Choice Q" Then TestRoutineChoiceQ: ClearKeyboard 3
    ElseIf in$ = "Choice R" Then TestRoutineChoiceR: ClearKeyboard 3
    ElseIf in$ = "Choice S" Then TestRoutineChoiceS: ClearKeyboard 3
    ElseIf in$ = "Choice T" Then TestRoutineChoiceT: ClearKeyboard 3
    ElseIf in$ = "Choice U" Then TestRoutineChoiceU: ClearKeyboard 3
    ElseIf in$ = "Choice V" Then TestRoutineChoiceV: ClearKeyboard 3
    ElseIf in$ = "Choice W" Then TestRoutineChoiceW: ClearKeyboard 3
    ElseIf in$ = "Choice X" Then TestRoutineChoiceX: ClearKeyboard 3
    ElseIf in$ = "Choice Y" Then TestRoutineChoiceY: ClearKeyboard 3
    ElseIf in$ = "Choice Z" Then TestRoutineChoiceZ: ClearKeyboard 3

        ' *** END ADD YOUR MENU CHOICES AND WHAT CODE RUNS HERE ***********************
        ' *****************************************************************************

    Else
        ' (DO NOTHING)
    End If
End Sub ' DoMenuItem

' ################################################################################################################################################################
' END ADD YOUR CUSTOM MENU ITEMS HERE @MENU2
' ################################################################################################################################################################

' ################################################################################################################################################################
' BEGIN YOUR CODE THAT THE MENU RUNS GOES HERE
' ################################################################################################################################################################

Sub TestRoutineChoiceA
    Dim in$
    Cls
    Print "This is TestRoutineChoiceA"
    Input "PRESS ENTER TO RETURN TO MENU"; in$
End Sub ' TestRoutineChoiceA

Sub TestRoutineChoiceB
    Dim in$
    Cls
    Print "This is TestRoutineChoiceB"
    Input "PRESS ENTER TO RETURN TO MENU"; in$
End Sub ' TestRoutineChoiceB

Sub TestRoutineChoiceC
    Dim in$
    Cls
    Print "This is TestRoutineChoiceC"
    Input "PRESS ENTER TO RETURN TO MENU"; in$
End Sub ' TestRoutineChoiceC

Sub TestRoutineChoiceD
    Dim in$
    Cls
    Print "This is TestRoutineChoiceD"
    Input "PRESS ENTER TO RETURN TO MENU"; in$
End Sub ' TestRoutineChoiceD

Sub TestRoutineChoiceE
    Dim in$
    Cls
    Print "This is TestRoutineChoiceE"
    Input "PRESS ENTER TO RETURN TO MENU"; in$
End Sub ' TestRoutineChoiceE

Sub TestRoutineChoiceF
    Dim in$
    Cls
    Print "This is TestRoutineChoiceF"
    Input "PRESS ENTER TO RETURN TO MENU"; in$
End Sub ' TestRoutineChoiceF

Sub TestRoutineChoiceG
    Dim in$
    Cls
    Print "This is TestRoutineChoiceG"
    Input "PRESS ENTER TO RETURN TO MENU"; in$
End Sub ' TestRoutineChoiceG

Sub TestRoutineChoiceH
    Dim in$
    Cls
    Print "This is TestRoutineChoiceH"
    Input "PRESS ENTER TO RETURN TO MENU"; in$
End Sub ' TestRoutineChoiceH

Sub TestRoutineChoiceI
    Dim in$
    Cls
    Print "This is TestRoutineChoiceI"
    Input "PRESS ENTER TO RETURN TO MENU"; in$
End Sub ' TestRoutineChoiceI

Sub TestRoutineChoiceJ
    Dim in$
    Cls
    Print "This is TestRoutineChoiceJ"
    Input "PRESS ENTER TO RETURN TO MENU"; in$
End Sub ' TestRoutineChoiceJ

Sub TestRoutineChoiceK
    Dim in$
    Cls
    Print "This is TestRoutineChoiceK"
    Input "PRESS ENTER TO RETURN TO MENU"; in$
End Sub ' TestRoutineChoiceK

Sub TestRoutineChoiceL
    Dim in$
    Cls
    Print "This is TestRoutineChoiceL"
    Input "PRESS ENTER TO RETURN TO MENU"; in$
End Sub ' TestRoutineChoiceL

Sub TestRoutineChoiceM
    Dim in$
    Cls
    Print "This is TestRoutineChoiceM"
    Input "PRESS ENTER TO RETURN TO MENU"; in$
End Sub ' TestRoutineChoiceM

Sub TestRoutineChoiceN
    Dim in$
    Cls
    Print "This is TestRoutineChoiceN"
    Input "PRESS ENTER TO RETURN TO MENU"; in$
End Sub ' TestRoutineChoiceN

Sub TestRoutineChoiceO
    Dim in$
    Cls
    Print "This is TestRoutineChoiceO"
    Input "PRESS ENTER TO RETURN TO MENU"; in$
End Sub ' TestRoutineChoiceO

Sub TestRoutineChoiceP
    Dim in$
    Cls
    Print "This is TestRoutineChoiceP"
    Input "PRESS ENTER TO RETURN TO MENU"; in$
End Sub ' TestRoutineChoiceP

Sub TestRoutineChoiceQ
    Dim in$
    Cls
    Print "This is TestRoutineChoiceQ"
    Input "PRESS ENTER TO RETURN TO MENU"; in$
End Sub ' TestRoutineChoiceQ

Sub TestRoutineChoiceR
    Dim in$
    Cls
    Print "This is TestRoutineChoiceR"
    Input "PRESS ENTER TO RETURN TO MENU"; in$
End Sub ' TestRoutineChoiceR

Sub TestRoutineChoiceS
    Dim in$
    Cls
    Print "This is TestRoutineChoiceS"
    Input "PRESS ENTER TO RETURN TO MENU"; in$
End Sub ' TestRoutineChoiceS

Sub TestRoutineChoiceT
    Dim in$
    Cls
    Print "This is TestRoutineChoiceT"
    Input "PRESS ENTER TO RETURN TO MENU"; in$
End Sub ' TestRoutineChoiceT

Sub TestRoutineChoiceU
    Dim in$
    Cls
    Print "This is TestRoutineChoiceU"
    Input "PRESS ENTER TO RETURN TO MENU"; in$
End Sub ' TestRoutineChoiceU

Sub TestRoutineChoiceV
    Dim in$
    Cls
    Print "This is TestRoutineChoiceV"
    Input "PRESS ENTER TO RETURN TO MENU"; in$
End Sub ' TestRoutineChoiceV

Sub TestRoutineChoiceW
    Dim in$
    Cls
    Print "This is TestRoutineChoiceW"
    Input "PRESS ENTER TO RETURN TO MENU"; in$
End Sub ' TestRoutineChoiceW

Sub TestRoutineChoiceX
    Dim in$
    Cls
    Print "This is TestRoutineChoiceX"
    Input "PRESS ENTER TO RETURN TO MENU"; in$
End Sub ' TestRoutineChoiceX

Sub TestRoutineChoiceY
    Dim in$
    Cls
    Print "This is TestRoutineChoiceY"
    Input "PRESS ENTER TO RETURN TO MENU"; in$
End Sub ' TestRoutineChoiceY

Sub TestRoutineChoiceZ
    Dim in$
    Cls
    Print "This is TestRoutineChoiceZ"
    Input "PRESS ENTER TO RETURN TO MENU"; in$
End Sub ' TestRoutineChoiceZ

' ################################################################################################################################################################
' END YOUR CODE THAT THE MENU RUNS GOES HERE
' ################################################################################################################################################################

' ################################################################################################################################################################
' BEGIN GENERIC MENU CODE #MENU1
' ################################################################################################################################################################

' /////////////////////////////////////////////////////////////////////////////
' TODO: fix bug where it blows up when less menu items are used
' TODO: remove unused variables
' TODO: use _Height(0) to automatically set height iMenuSize and iInfoSize
' TODO: use variables to make it easy to change placement and layout of title/instructions/description
' DONE: use _Width(0) to automatically limit # of text columns

Sub main
    Dim RoutineName As String: RoutineName = "main"
    Dim sResult As String
    Dim sFileName As String
    Dim vbCrLf As String: vbCrLf = Chr$(10) + Chr$(13)
    Dim vbCr As String: vbCr = Chr$(13)
    Dim vbLf As String: vbLf = Chr$(10)
    Dim vbTab As String: vbTab = Chr$(9)
    Dim quot As String: quot = Chr$(34)
    Dim sTemp As String
    Dim sTempHR As String: sTempHR = "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++"
    Dim sOut As String
    Dim sComment As String
    Dim sError As String
    Dim bFinished As Integer
    Dim bAppend As Integer
    Dim iMenuSize As Integer ' how many items to display on screen
    Dim iMenuPos As Integer ' where in the list we are
    Dim iMenuStart As Integer ' first item to display on the list
    Dim iMenuEnd As Integer ' last item to display on the list
    Dim iMenuLoop As Integer
    Dim iStartRow As Integer
    Dim iRow As Integer
    Dim iCol As Integer
    Dim iColCount As Integer
    Dim iRowCount As Integer
    Dim iLastKey As Integer
    Dim iPageSize As Integer
    Dim iNudgeSize As Integer ' when cursor reaches bottom or top, how many lines to scroll
    Dim bMoved As Integer
    Dim bInitPage As Integer
    Dim bInitInfo As Integer
    Dim in$
    ReDim arrInfo(-1) As String
    Dim sInfoDelim As String: sInfoDelim = "\n"
    Dim iInfoRow As Integer
    Dim iInfoSize As Integer
    Dim iNextRow As Integer
    'Dim iLastInfoRow As Integer
    Dim iBackColor~&: iBackColor~& = cBlack
    Dim iTitleFgColor~&: iTitleFgColor~& = cBlack
    Dim iTitleBgColor~&: iTitleBgColor~& = cDodgerBlue
    Dim iInstructColor~&: iInstructColor~& = cCyan
    Dim iMenuColor~&: iMenuColor~& = cWhite
    Dim iRunColor~&: iRunColor~& = cYellow
    Dim iInfoColor~&: iInfoColor~& = cSilver
    Dim iMaxColumns As Integer
    Dim iIndex As Integer

    ' SET UP SCREEN
    ' MAKE SCREEN BIG TO FIT A LOT OF TEXT: 1024x768=128cols,48rows and 1280x1024=160cols,64rows
    Screen _NewImage(1024, 768, 32): _ScreenMove 0, 0

    ' INITIALIZE
    Cls
    Print "Initializing..."
    InitializeGlobal

    iRowCount = _Height(0) \ _FontHeight
    iColCount = _Width(0) \ _FontWidth

    iMenuSize = 20
    iNudgeSize = iMenuSize \ 2
    iPageSize = iMenuSize - iNudgeSize
    iMenuPos = LBound(m_arrMenu)
    iMenuStart = iMenuPos
    iMaxColumns = iColCount - 1
    'sFileName = m_ProgramPath$ + Left$(m_ProgramName$, Len(m_ProgramName$) - 4) + ".txt"
    iStartRow = 7
    iInfoRow = iMenuSize + 10
    iInfoSize = 10

    ' MAIN MENU
    bInitPage = TRUE
    bInitInfo = TRUE
    iLastKey = 0
    bMoved = TRUE
    bFinished = FALSE
    Do
        ' SHOW INSTRUCTIONS
        If bInitPage = TRUE Then
            Cls , iBackColor~& ' makes the background opaque black
            Color iTitleFgColor~&, iTitleBgColor~&
            'PrintString 0, 0, "SimpleMenu"
            PrintString 0, 0, "Fast Zap 'Em : Multispacewar! by Softintheheadware, 2022"

            Color iInstructColor~&, iBackColor~&
            PrintString 2, 0, "KEY(S)                                ACTION"
            PrintString 3, 0, "-----------------------------------   --------------------------------"
            PrintString 4, 0, "Crsr Up/Down, PgUp/PgDown, Home/End   Navigate/select item"
            'PrintString 5, 0, "Crsr Left                             See description of current item"
            PrintString 6, 0, "Crsr Right                            Run current item"

            ClearKeyboard 1
            bInitPage = FALSE
        End If

        If bInitInfo = TRUE Or bMoved = TRUE Then
            ' Clear old description
            For iNextRow = iInfoRow To (iInfoRow + iInfoSize)
                Locate iNextRow, 1
                Color iBackColor~&, iBackColor~&
                Print String$(iMaxColumns, " ");
            Next iNextRow

            ' Show current item's description
            If Len(m_arrMenu(iMenuPos).Choice) > 0 Then
                If Len(m_arrMenu(iMenuPos).Info) > 0 Then
                    split m_arrMenu(iMenuPos).Info, sInfoDelim, arrInfo()
                    iRowCount = 0
                    iNextRow = iInfoRow
                    For iIndex = 0 To UBound(arrInfo)
                        iRowCount = iRowCount + 1
                        If iRowCount > iInfoSize Then Exit For
                        Locate iNextRow, 1
                        Color iInfoColor~&, iBackColor~&
                        Print Left$(arrInfo(iIndex), iMaxColumns);
                        iNextRow = iNextRow + 1
                    Next iIndex
                End If
            End If ' If Len(m_arrMenu(iMenuPos).Choice) > 0 Then

            bInitInfo = FALSE
        End If

        ' (RE)DISPLAY CURRENT SLICE OF THE MENU
        If bMoved = TRUE Then
            iRow = iStartRow
            iCol = 0
            If iMenuStart < LBound(m_arrMenu) Then
                iMenuStart = LBound(m_arrMenu)
            End If
            iMenuEnd = (iMenuStart + iMenuSize) - 1
            If iMenuEnd > UBound(m_arrMenu) Then
                If iMenuSize >= UBound(m_arrMenu) Then
                    iMenuStart = UBound(m_arrMenu) - (iMenuSize - 1)
                Else
                    iMenuStart = LBound(m_arrMenu)
                    iMenuEnd = UBound(m_arrMenu)
                End If
            End If
            For iMenuLoop = iMenuStart To iMenuEnd
                iRow = iRow + 1
                If iMenuLoop = iMenuPos Then
                    Color iBackColor~&, iMenuColor~&
                Else
                    Color iMenuColor~&, iBackColor~&
                End If
                PrintString iRow, iCol, right$("   " + cstr$(iMenuLoop), 3) + ". " + _
                    left$(m_arrMenu(iMenuLoop).Choice + string$(iColCount, " "), iColCount)
            Next iMenuLoop
            bMoved = FALSE
        End If

        ' GET USER INPUT
        While _DeviceInput(1): Wend ' Clear and update the keyboard buffer

        ' DON'T ACCEPT ANY MORE INPUT UNTIL THE LAST PRESSED KEY IS RELEASED
        If iLastKey <> 0 Then
            If _Button(iLastKey) = FALSE Then
                iLastKey = 0
            End If
        End If

        ' READY TO ACCEPT MORE INPUT?
        If iLastKey = 0 Or bInitInfo = TRUE Then
            ' DID PLAYER PRESS ANY KEYS WE KNOW?
            If _Button(KeyCode_Home%) Then
                in$ = "home"
                iLastKey = KeyCode_Home%
            ElseIf _Button(KeyCode_End%) Then
                in$ = "end"
                iLastKey = KeyCode_End%
            ElseIf _Button(KeyCode_PgUp%) Then
                in$ = "pgup"
                iLastKey = KeyCode_PgUp%
            ElseIf _Button(KeyCode_PgDn%) Then
                in$ = "pgdn"
                iLastKey = KeyCode_PgDn%
            ElseIf _Button(KeyCode_Up%) Then
                in$ = "up"
                iLastKey = KeyCode_Up%
            ElseIf _Button(KeyCode_Down%) Then
                in$ = "down"
                iLastKey = KeyCode_Down%
                'ElseIf _Button(KeyCode_Left%) Then
                '    in$ = "info"
                '    iLastKey = KeyCode_Left%
            ElseIf _Button(KeyCode_Right%) Then
                in$ = "run"
                iLastKey = KeyCode_Right%
                'ElseIf _Button(KeyCode_Enter%) Then '<-- for some reason clearing the keyboard buffer doesn't stop the Enter key from being detected later, oh well
                '    in$ = "run"
                '    iLastKey = KeyCode_Enter%
            ElseIf _Button(KeyCode_Escape%) Then
                in$ = "esc"
                iLastKey = KeyCode_Escape%
            Else
                in$ = ""
            End If

            ' IF USER DID PRESS A KEY WE KNOW, PROCESS INPUT
            If iLastKey <> 0 Or bInitInfo = TRUE Then
                ClearKeyboard 0

                If in$ = "" Then
                    ' (DO NOTHING)
                ElseIf in$ = "home" Then
                    iMenuPos = LBound(m_arrMenu)
                    bMoved = TRUE
                ElseIf in$ = "end" Then
                    iMenuPos = UBound(m_arrMenu)
                    bMoved = TRUE
                ElseIf in$ = "pgup" Then
                    iMenuPos = iMenuPos - iPageSize
                    bMoved = TRUE
                ElseIf in$ = "pgdn" Then
                    iMenuPos = iMenuPos + iPageSize
                    bMoved = TRUE
                ElseIf in$ = "up" Then
                    iMenuPos = iMenuPos - 1
                    bMoved = TRUE
                ElseIf in$ = "down" Then
                    iMenuPos = iMenuPos + 1
                    bMoved = TRUE
                ElseIf in$ = "run" Then
                    '' HIGHLIGHT NAME
                    'iRow = iStartRow
                    'For iMenuLoop = iMenuStart To iMenuEnd
                    '    iRow = iRow + 1
                    '    If iMenuLoop = iMenuPos Then
                    '        Color iBackColor~&, iRunColor~&
                    '        PrintString iRow, iCol, right$("   " + cstr$(iMenuLoop), 3) + ". " + _
                    '            left$(m_arrMenu(iMenuLoop).Choice + string$(iColCount, " "), iColCount)
                    '        Exit For
                    '    End If
                    'Next iMenuLoop

                    ' DO WHAT THE USER SELECTED
                    DoMenuItem iMenuPos
                    bMoved = TRUE

                    ' FLAG TO REDRAW MENU
                    bInitPage = TRUE
                    bInitInfo = TRUE

                ElseIf in$ = "esc" Then
                    bFinished = TRUE
                    Exit Do
                End If

                ' HANDLE MOVE
                If bMoved = TRUE Then
                    ' MAKE SURE NOT OUT OF BOUNDS
                    If iMenuPos < LBound(m_arrMenu) Then
                        iMenuPos = LBound(m_arrMenu)
                    ElseIf iMenuPos > UBound(m_arrMenu) Then
                        iMenuPos = UBound(m_arrMenu)
                    End If

                    ' DETERMINE WHAT RANGE TO DISPLAY
                    If iMenuPos < iMenuStart Then
                        iMenuStart = iMenuPos - iNudgeSize
                        If iMenuStart < LBound(m_arrMenu) Then
                            iMenuStart = LBound(m_arrMenu)
                        End If
                        iMenuEnd = iMenuStart + (iMenuSize - 1)
                        If iMenuEnd > UBound(m_arrMenu) Then
                            iMenuEnd = UBound(m_arrMenu)
                        End If
                    ElseIf iMenuPos > iMenuEnd Then
                        iMenuEnd = iMenuPos + iNudgeSize
                        If iMenuEnd > UBound(m_arrMenu) Then
                            iMenuEnd = UBound(m_arrMenu)
                        End If
                        iMenuStart = iMenuEnd - (iMenuSize - 1)
                        If iMenuStart < LBound(m_arrMenu) Then
                            iMenuStart = LBound(m_arrMenu)
                        End If
                    End If
                End If ' HANDLE MOVE

            End If ' iLastKey <> 0
        End If ' IF iLastKey = 0
    Loop Until bFinished = TRUE

    While _DeviceInput(1): Wend ' Clear and update the keyboard buffer
    ClearKeyboard 3

    ' RETURN TO TEXT SCREEN
    Screen 0

End Sub ' main

' /////////////////////////////////////////////////////////////////////////////

Sub AddNextMenuItem (sName As String, sInfo As String)
    ReDim _Preserve m_arrMenu(1 To UBound(m_arrMenu) + 1) As MenuType
    m_arrMenu(UBound(m_arrMenu)).Choice = sName
    m_arrMenu(UBound(m_arrMenu)).Info = sInfo
End Sub ' AddNextMenuItem

' /////////////////////////////////////////////////////////////////////////////
' Tries to clear the keyboard buffer.
' In some places _KeyClear seems to work
' but in other situations While_DeviceInput(1):Wend works
' And in other situations k = _KeyHit works.

' So this handy dandy sub does it all:

' iDelay% VALUE    FOR
' -------------    ---
' (any)            _KeyClear
' 1                _Delay 1
' 2                 While _DeviceInput(1): Wend
' 3                 k = _KeyHit and the above methods

Sub ClearKeyboard (iDelay%)
    Dim k As Integer
    _KeyClear
    If iDelay% = 1 Then
        _Delay iDelay%
    End If
    If iDelay% > 1 Then
        While _DeviceInput(1): Wend ' Clear and update the keyboard buffer
    End If
    If iDelay% > 2 Then
        k = _KeyHit
    End If
End Sub ' ClearKeyboard

' ################################################################################################################################################################
' END GENERIC MENU CODE @MENU1
' ################################################################################################################################################################

' ****************************************************************************************************************************************************************
' BEGIN SIMPLE VECTOR ENGINE #VEC
' ****************************************************************************************************************************************************************

' /////////////////////////////////////////////////////////////////////////////

Sub SinCosTest1
    Dim iAngle As Integer
    Dim sngX As Single
    Dim sngY As Single
    Dim sngDX As Single
    Dim sngDY As Single
    Dim iWidth As Integer: iWidth = 12
    Dim sDelim As String: sDelim = "   "
    'String$(toWidth%, padChar$)
   
    Cls
    Print "Angle   Sin       Cos       SinD      CosD      "
    Print "-----   -------   -------   -------   -------   "
    print _
        LeftPadString$("Angle", iWidth, " ") + sDelim + _
        LeftPadString$("Sin"  , iWidth, " ") + sDelim + _
        LeftPadString$("Cos"  , iWidth, " ") + sDelim + _
        LeftPadString$("SinD" , iWidth, " ") + sDelim + _
        LeftPadString$("CosD" , iWidth, " ") + sDelim + _
        ""
   
    For iAngle = 0 To 270 Step 45
        sgnX = Sin(iAngle)
        sngDX = Sin(_D2R(iAngle))

        sgnY = Cos(iAngle)
        sngDY = Cos(_D2R(iAngle))
       
        'SinD = Sin(_D2R(iAngle))
        'CosD = Cos(_D2R(degrees))
       
        print _
            LeftPadString$(cstr$(iAngle)   , iWidth, " ") + sDelim + _
            LeftPadString$(SngRoundedToStr$(sgnX, iWidth) , iWidth, " ") + sDelim + _
            LeftPadString$(SngRoundedToStr$(sgnY, iWidth) , iWidth, " ") + sDelim + _
            LeftPadString$(SngRoundedToStr$(sngDX, iWidth), iWidth, " ") + sDelim + _
            LeftPadString$(SngRoundedToStr$(sngDY, iWidth), iWidth, " ") + sDelim + _
            ""
    Next iAngle
   
    Print "Press any key to continue"
    Sleep
End Sub ' SinCosTest1

' /////////////////////////////////////////////////////////////////////////////
' plot 4 points (4 corners of a square)

' when user presses "-" rotate square counter-clockwise
' when user presses "+" rotate square clockwise

' arrow keys move square around screen

Sub RotatePointsTest1
    Dim RoutineName As String: RoutineName = "RotatePointsTest1"
    'Dim iFPS As Integer: iFPS = 60
    Dim iX As Integer
    Dim iY As Integer
    Dim iSize As Integer
    Dim iHalfSize As Integer
    'Dim iMinX As Integer: iMinX = 0
    'Dim iMaxX As Integer: iMaxX = 800
    'Dim iMinY As Integer: iMinY = 0
    'Dim iMaxY As Integer: iMaxY = 640
    Dim iLoop As Integer
    Dim iAngle As Integer
    Dim sngRadians As Single
    Dim sngX As Integer
    Dim sngY As Integer
    Dim sngDX As Integer
    Dim sngDY As Integer
    Dim x1, y1, x2, y2 As Integer
    ReDim arrPoints(1 To 4) As PointsType
    Dim sKey As String
    Dim iRow As Integer
    Dim bQuit As Integer: bQuit = FALSE
    Dim in$
   
    iFPS = 60
    'iMinX = 1
    'iMaxX = 800
    'iMinY = 1
    'iMaxY = 640
   
    Screen _NewImage(iMaxX, iMaxY, 32) ' 100 text columns x 40 text rows
    _KeyClear
   
    iX = 475: iY = 375
    iSize = 50: iHalfSize = (iSize / 2) - 1
   
    arrPoints(1).x = 0 - iHalfSize: arrPoints(1).rx = arrPoints(1).x
    arrPoints(1).y = 0 - iHalfSize: arrPoints(1).ry = arrPoints(1).y
    arrPoints(1).color = cRed
    arrPoints(1).angle = 0
   
    arrPoints(2).x = 0 + iHalfSize: arrPoints(2).rx = arrPoints(2).x
    arrPoints(2).y = 0 - iHalfSize: arrPoints(2).ry = arrPoints(2).y
    arrPoints(2).color = cLime
    arrPoints(2).angle = 0
   
    arrPoints(3).x = 0 - iHalfSize: arrPoints(3).rx = arrPoints(3).x
    arrPoints(3).y = 0 + iHalfSize: arrPoints(3).ry = arrPoints(3).y
    arrPoints(3).color = cCyan
    arrPoints(3).angle = 0
   
    arrPoints(4).x = 0 + iHalfSize: arrPoints(4).rx = arrPoints(4).x
    arrPoints(4).y = 0 + iHalfSize: arrPoints(4).ry = arrPoints(4).y
    arrPoints(4).color = cYellow
    arrPoints(4).angle = 0
   
    iAngle = 0
    While TRUE = TRUE
        'For iAngle = 0 To 359
        ' CLEAR SCREEN
        _Dest 0: Cls , cBlack
           
        ' SHOW INSTRUCTIONS
        PrintAt 1, 1, "Press arrow keys to rotate"
           
        PrintAt 3, 1, "iAngle: " + cstr$(iAngle)
           
        PrintAt 20, 1, "Press ESC to exit."
           
        ' DRAW SHAPE
        For iLoop = 1 To 4
            'DrawCircleSolid iX + arrPoints(iLoop).rx, iY + arrPoints(iLoop).ry, 8, arrPoints(iLoop).color
            DrawCircleSolid arrPoints(iLoop).rx, arrPoints(iLoop).ry, 8, arrPoints(iLoop).color
               
            x1 = arrPoints(iLoop).rx
            y1 = arrPoints(iLoop).ry
            If iLoop < 4 Then
                x2 = arrPoints(iLoop + 1).rx
                y2 = arrPoints(iLoop + 1).ry
            Else
                x2 = arrPoints(1).rx
                y2 = arrPoints(1).ry
            End If
               
            Line (x1, y1)-(x2, y2), cWhite
               
        Next iLoop
           
           
        ' ROTATE POINTS
        For iLoop = 1 To 4
            arrPoints(iLoop).angle = iAngle
               
            ' rotate the points based on angle iAngle
            arrPoints(iLoop).rx = iX + (arrPoints(iLoop).x * SinD(iAngle))
            arrPoints(iLoop).ry = iY + (arrPoints(iLoop).y * CosD(iAngle))
               
            'iRow = 5 + ( (iLoop-1) * 5)
            'PrintAt  iRow + 0, 1, "#1: x                           =" + cstr$(arrPoints(iLoop).x ) + "  y=" + cstr$(arrPoints(iLoop).y )
            'PrintAt  iRow + 1, 1, "    sgnX  = " + _Trim$(Str$(sgnX))  + " sgnY  = " + _Trim$(Str$(sgnY))
            'PrintAt  iRow + 2, 1, "    sngDX = " + _Trim$(Str$(sngDX)) + " sngDY = " + _Trim$(Str$(sngDY))
            'PrintAt  iRow + 3, 1, "    rx                          =" + cstr$(arrPoints(iLoop).rx) + " ry=" + cstr$(arrPoints(iLoop).ry)
               
        Next iLoop
           
        ' PROCESS INPUT
        While _DeviceInput(1): Wend ' clear and update the keyboard buffer
        sKey = ""
           
        ' ROTATE?
        If _Button(KeyCode_Minus%) Then
            iAngle = iAngle - 1
        ElseIf _Button(KeyCode_Equal%) Then
            iAngle = iAngle + 1
        End If
        If iAngle < 0 Then iAngle = 359
        If iAngle > 359 Then iAngle = 0
           
        ' MOVE LEFT/RIGHT
        If _Button(KeyCode_Left%) Then
            iX = iX - 1
        ElseIf _Button(KeyCode_Right%) Then
            iX = iX + 1
        End If
        If iX < iMinX Then
            iX = iMaxX
        ElseIf iX > iMaxX Then
            iX = iMinX
        End If
           
        ' MOVE UP/DOWN
        If _Button(KeyCode_Up%) Then
            iY = iY - 1
        ElseIf _Button(KeyCode_Down%) Then
            iY = iY + 1
        End If
        If iY < iMinY Then
            iY = iMaxY
        ElseIf iY > iMaxY Then
            iY = iMinY
        End If
           
        ' QUIT?
        If _Button(KeyCode_Escape%) Then
            bQuit = TRUE
            '        Exit For
        End If

        ' CLEAR KEYBOARD BUFFER
        _KeyClear
           
        ' UPDATE THE SCREEN
        _Display
           
        ' CONTROL GAME SPEED
        _Limit iFPS
           
        '    If bQuit = TRUE Then Exit For
        'Next iAngle
       
        If bQuit = TRUE Then Exit While
    Wend
   
    ' RETURN TO AUTODISPLAY
    _AutoDisplay
   
    'Input "Press <ENTER> to continue", in$
End Sub ' RotatePointsTest1

' /////////////////////////////////////////////////////////////////////////////
' Precalculates dx,dy for 0-359 degrees
' and returns as 2 arrays (one for dx, one for dy)

' Helped by code from
' https://wiki.qb64.dev/qb64wiki/index.php/SIN

Sub Calculate_DX_DY_per_angle (arrAngleToDX() As Single, arrAngleToDY() As Single)
    Dim PI As Single
    Dim iDegree As Integer
    Dim sngRadians As Single
    Dim iAngle As Integer
    Dim iDiff As Integer

    ' Make sure arrays are dimensioned
    ReDim arrAngleToDX(0 To 359) As Single
    ReDim arrAngleToDY(0 To 359) As Single

    ' Calculate Pi
    PI = 4 * Atn(1)

    ' Calculate dx,dy for each of 360 degrees
    For iDegree = 0 To 359
        ' re-orient so 0 degrees is 12 o'clock, 180 degrees is 6 o'clock
        If iDegree <= 180 Then
            iAngle = 180 - iDegree
        ElseIf iDegree = 181 Then
            iDiff = 178
            iAngle = iDegree + iDiff
        Else
            iDiff = iDiff - 2
            iAngle = iDegree + iDiff
        End If

        ' calculate dx, dy for the current angle
        sngRadians = iDegree * PI / 180
        arrAngleToDX(iAngle) = Sin(sngRadians)
        arrAngleToDY(iAngle) = Cos(sngRadians)
    Next iDegree
End Sub ' Calculate_DX_DY_per_angle

'Sub Calculate_DX_DY_per_angle_TEST_1
'   Dim arrAngleToDX(0 To 359) As Single
'   Dim arrAngleToDY(0 To 359) As Single
'   Dim iAngle As Integer
'    Dim in$
'
'   Calculate_DX_DY_per_angle arrAngleToDX(), arrAngleToDY()
'
'   for iAngle = 0 to 359
'       DebugPrint _
'           LeftPadString$(cstr$(iAngle), 3, " ") + " deg. " + _
'           "DX=" + SngRoundedToStr$(arrAngleToDX(iAngle), 6) + " " + _
'           "DY=" + SngRoundedToStr$(arrAngleToDY(iAngle), 6) + " " + _
'           ""
'   next iAngle
'
'    Input "Press <ENTER> to continue", in$
'End Sub ' Calculate_DX_DY_per_angle_TEST_1

' /////////////////////////////////////////////////////////////////////////////

Sub Calculate_DX_DY_per_angle_TEST_2
    Dim RoutineName As String: RoutineName = "Calculate_DX_DY_per_angle_TEST_2"
    'Dim iFPS As Integer: iFPS = 120
    'Dim iMinX As Integer: iMinX = 0
    'Dim iMaxX As Integer: iMaxX = 800
    'Dim iMinY As Integer: iMinY = 0
    'Dim iMaxY As Integer: iMaxY = 640
    Dim arrAngleToDX(0 To 359) As Single
    Dim arrAngleToDY(0 To 359) As Single
    Dim iAngle As Integer
    Dim iX As Integer
    Dim iY As Integer
    Dim sngX As Single
    Dim sngY As Single
    Dim sngVX As Single: sngVX = 4
    Dim sngVY As Single: sngVY = 4
    Dim sngDX As Single
    Dim sngDY As Single
    Dim bQuit As Integer: bQuit = FALSE
    Dim in$
   
    iFPS = 120
    iMinX = 1
    iMaxX = 1024
    iMinY = 1
    iMaxY = 768
   
    Calculate_DX_DY_per_angle arrAngleToDX(), arrAngleToDY()

    Screen _NewImage(iMaxX, iMaxY, 32) ' 100 text columns x 40 text rows
    _KeyClear

    While TRUE = TRUE
        For iAngle = 0 To 359

            ' CALCULATE DIRECTION FOR ANGLE
            sngDX = sngVX * arrAngleToDX(iAngle)
            sngDY = sngVY * arrAngleToDY(iAngle)

            ' Start in center
            iX = iMaxX \ 2: sngX = iX
            iY = iMaxY \ 2: sngY = iY

            ' Move object outward at current iAngle
            While TRUE = TRUE
                ' CLEAR SCREEN
                _Dest 0: Cls , cBlack

                ' DRAW CIRCLE
                ' CIRCLE (x, y), radius, color
                'CIRCLE [[[STEP]]](column, row), radius%, [drawColor%][, startRadian!, stopRadian!] [, aspect!]
                'Circle (dblX + 4, dblY + 8), 4, cGray
                iX = SngToInt%(sngX)
                iY = SngToInt%(sngY)

                'Line (iLoop * 8, iHeight * 16)-(iLoop * 8 + 8, _Height), cGray, BF
                'Circle (iX, iY), 4, cRed
                DrawCircleSolid iX, iY, 8, cRed

                ' SHOW VALUES
                PrintAt 1, 1, RoutineName
                PrintAt 3, 1, _
                    "iAngle=" + LeftPadString$(cstr$(iAngle), 3, " ")
                PrintAt 5, 1, _
                    "iX    =" + LeftPadString$(cstr$(iX), 3, " ") + " " + _
                    "sngDX =" + SngRoundedToStr$(sngDX, 6)
                PrintAt 7, 1, _
                    "iY    =" + LeftPadString$(cstr$(iY), 3, " ") + " " + _
                    "sngDY =" + SngRoundedToStr$(sngDY, 6)

                Color cWhite, cBlue
                PrintAt 9, 1, "Press ESC to exit."
                Color cWhite, cEmpty

                ' MOVE OBJECT
                sngX = sngX + sngDX
                sngY = sngY + sngDY

                ' PROCESS INPUT
                While _DeviceInput(1): Wend ' clear and update the keyboard buffer

                ' IF OUT OF BOUNDS, GOTO NEXT ANGLE
                If iX < 0 Or iX > iMaxX Or iY < 0 Or iY > iMaxY Then
                    Exit While
                End If
                'If _Button(KeyCode_A%) Then
                '   Exit While
                'end if

                ' QUIT?
                If _Button(KeyCode_Escape%) Then
                    bQuit = TRUE
                    Exit While
                End If

                ' CLEAR KEYBOARD BUFFER
                _KeyClear

                ' UPDATE THE SCREEN
                _Display

                ' CONTROL GAME SPEED
                _Limit iFPS
            Wend

            If bQuit = TRUE Then Exit For
        Next iAngle

        If bQuit = TRUE Then Exit While
    Wend

    ' RETURN TO AUTODISPLAY
    _AutoDisplay

    'Input "Press <ENTER> to continue", in$
End Sub ' Calculate_DX_DY_per_angle_TEST_2

' /////////////////////////////////////////////////////////////////////////////

Sub DrawVectorObjectTest1
    Dim RoutineName As String: RoutineName = "DrawVectorObjectTest1"
    'Dim iFPS As Integer: iFPS = 120
    Dim iLoop As Integer
    Dim iObject As Integer
    Dim iLine As Integer
    Dim imgBack&
    Dim imgMiddle&
    Dim imgFront&
    Dim iWhich As Integer: iWhich = 1
    Dim bQuit As Integer: bQuit = FALSE
    Dim in$
    Dim sError As String: sError = ""
    Dim iX As Integer
    Dim iY As Integer
    Dim sKey As String
    'Dim iMinX As Integer: iMinX = 0
    'Dim iMaxX As Integer: iMaxX = 800
    'Dim iMinY As Integer: iMinY = 0
    'Dim iMaxY As Integer: iMaxY = 640
    Dim iPrintX As Integer
    Dim iPrintY As Integer

    ' =============================================================================
    ' INITIALIZE
    iFPS = 120
    iMinX = 1
    iMaxX = 1024
    iMinY = 1
    iMaxY = 768
   
    Screen _NewImage(iMaxX, iMaxY, 32) ' 100 text columns x 40 text rows
    'imgBack& = _NewImage(800, 640, 32) ' background
    'imgMiddle& = _NewImage(800, 640, 32) ' other stuff
    'imgFront& = _NewImage(800, 640, 32) ' foreground

    ' =============================================================================
    ' START NEW GAME
    Do
        _KeyClear

        ' CONFIGURE PRINTING FOR _PrintString
        _PrintMode _FillBackground
        '_PrintMode _KEEPBACKGROUND

        ' INIT VARS
        sKey = ""
        iX = 0: iY = 0
        For iObject = LBound(m_arrObject) To UBound(m_arrObject)
            m_arrObject(iObject).IsEnabled = FALSE
            m_arrObject(iObject).x = iX
            m_arrObject(iObject).y = iY
            m_arrObject(iObject).dx = RandomNumber%(-5, 5)
            m_arrObject(iObject).dy = RandomNumber%(-5, 5)
            m_arrObject(iObject).cx = 0
            m_arrObject(iObject).cy = 0
            iX = iX + 200
            If iX > iMaxX Then
                iX = iMinX
                iY = iY + 200
                If iY > iMaxY Then
                    iY = iMinY
                    iX = 100
                End If
            End If
        Next iObject
        InitVectorObjects

        ' MAIN LOOP
        While TRUE = TRUE
            ' REDRAW BACKGROUND LAYERS
            DrawLayers imgBack&, imgMiddle&, imgFront&
            '_Dest 0: Cls , cBlack

            ' -----------------------------------------------------------------------------
            ' BEGIN SHOW VALUES ON SCREEN
            ' -----------------------------------------------------------------------------
            Color cWhite
            PrintAt 1, 1, RoutineName

            Color cYellow
            PrintAt 3, 1, "Press 1-6 to select active object."
            PrintAt 4, 1, "Arrow keys move active object."

            Color cWhite, cBlue
            PrintAt 5, 1, "Press ESC to exit."

            Color cWhite, cEmpty
            iPrintY = 7
            For iObject = LBound(m_arrObject) To UBound(m_arrObject)
                If m_arrObject(iObject).IsEnabled = TRUE Then
                    Color cCyan
                Else
                    Color cGray
                End If
                PrintAt iPrintY, 1, "" + _
                    "obj #" + cstr$(iObject) + _
                    "(" + cstr$(m_arrObject(iObject).x) + "," + cstr$(m_arrObject(iObject).y) + ")" + _
                    "(" + cstr$(m_arrObject(iObject).dx) + "," + cstr$(m_arrObject(iObject).dy) + ")" + _
                    "(" + cstr$(m_arrObject(iObject).cx) + "," + cstr$(m_arrObject(iObject).cy) + ")" + _
                    ""
                iPrintY = iPrintY + 1
            Next iObject

            ' SHOW INPUT
            Color cLime
            PrintAt 20, 1, "Controls   : " + RightPadString$(sKey, 10, " ") + "   "

            Color cWhite
            PrintAt 21, 1, "Object #   : " + cstr$(iWhich)
            ' -----------------------------------------------------------------------------
            ' END SHOW VALUES ON SCREEN
            ' -----------------------------------------------------------------------------

            ' MOVE + DRAW ENABLED OBJECTS
            For iObject = LBound(m_arrObject) To UBound(m_arrObject)
                ' Only enabled objects
                If m_arrObject(iObject).IsEnabled = TRUE Then

                    ' Move along X axis
                    m_arrObject(iObject).cx = m_arrObject(iObject).cx + 1
                    If m_arrObject(iObject).cx > (10 - Abs(m_arrObject(iObject).dx)) Then
                        m_arrObject(iObject).cx = 0
                        If m_arrObject(iObject).dx < 0 Then
                            m_arrObject(iObject).x = m_arrObject(iObject).x - 1
                            If m_arrObject(iObject).x < iMinX Then
                                m_arrObject(iObject).x = iMaxX
                            End If
                        ElseIf m_arrObject(iObject).dx > 0 Then
                            m_arrObject(iObject).x = m_arrObject(iObject).x + 1
                            If m_arrObject(iObject).x > iMaxX Then
                                m_arrObject(iObject).x = iMinX
                            End If
                        End If
                    End If

                    ' Move along Y axis
                    m_arrObject(iObject).cy = m_arrObject(iObject).cy + 1
                    If m_arrObject(iObject).cy > (10 - Abs(m_arrObject(iObject).dy)) Then
                        m_arrObject(iObject).cy = 0
                        If m_arrObject(iObject).dy < 0 Then
                            m_arrObject(iObject).y = m_arrObject(iObject).y - 1
                            If m_arrObject(iObject).y < iMinY Then
                                m_arrObject(iObject).y = iMaxY
                            End If
                        ElseIf m_arrObject(iObject).dy > 0 Then
                            m_arrObject(iObject).y = m_arrObject(iObject).y + 1
                            If m_arrObject(iObject).y > iMaxY Then
                                m_arrObject(iObject).y = iMinY
                            End If
                        End If
                    End If

                    ' Draw object's line segments
                    For iLine = LBound(m_arrLines, 2) To UBound(m_arrLines, 2)
                        'Line (x1%, y1%)-(x2%, y2%), arrColor(arrStars(iLoop).ColorIndex), BF
                        Line _
                            (m_arrObject(iObject).x + m_arrLines(iObject, iLine).x1, _
                            m_arrObject(iObject).y + m_arrLines(iObject, iLine).y1) _
                            - _
                            (m_arrObject(iObject).x + m_arrLines(iObject, iLine).x2, _
                            m_arrObject(iObject).y + m_arrLines(iObject, iLine).y2) _
                            , _
                            m_arrLines(iObject, iLine).color ' , BF
                        If m_arrLines(iObject, iLine).IsLast = TRUE Then
                            Exit For
                        End If
                    Next iLine

                End If
            Next iObject

            ' UPDATE THE SCREEN
            _Display

            ' PROCESS INPUT
            While _DeviceInput(1): Wend ' clear and update the keyboard buffer
            sKey = ""

            ' QUIT?
            If _Button(KeyCode_Escape%) Then
                bQuit = TRUE
                Exit While
            End If

            ' OTHER INPUT 1-6 SELECTS WHICH OBJECT TO CHANGE
            If _Button(KeyCode_1%) Then
                sKey = sKey + "1,"
                iWhich = 1
            ElseIf _Button(KeyCode_2%) Then
                sKey = sKey + "2,"
                iWhich = 2
            ElseIf _Button(KeyCode_3%) Then
                sKey = sKey + "3,"
                iWhich = 3
            ElseIf _Button(KeyCode_4%) Then
                sKey = sKey + "4,"
                iWhich = 4
            ElseIf _Button(KeyCode_5%) Then
                sKey = sKey + "5,"
                iWhich = 5
            ElseIf _Button(KeyCode_6%) Then
                sKey = sKey + "6,"
                iWhich = 6
            End If

            ' GET DIRECTION
            If _Button(KeyCode_Left%) Then
                sKey = sKey + "LEFT,"
                m_arrObject(iWhich).dx = m_arrObject(iWhich).dx - 1
                If m_arrObject(iWhich).dx < -10 Then m_arrObject(iWhich).dx = -10
            ElseIf _Button(KeyCode_Right%) Then
                sKey = sKey + "RIGHT,"
                m_arrObject(iWhich).dx = m_arrObject(iWhich).dx + 1
                If m_arrObject(iWhich).dx > 10 Then m_arrObject(iWhich).dx = 10
            ElseIf _Button(KeyCode_Up%) Then
                sKey = sKey + "UP,"
                m_arrObject(iWhich).dy = m_arrObject(iWhich).dy - 1
                If m_arrObject(iWhich).dy < -10 Then m_arrObject(iWhich).dy = -10
            ElseIf _Button(KeyCode_Down%) Then
                sKey = sKey + "DOWN,"
                m_arrObject(iWhich).dy = m_arrObject(iWhich).dy + 1
                If m_arrObject(iWhich).dy > 10 Then m_arrObject(iWhich).dy = 10
            End If

            ' CLEAR KEYBOARD BUFFER
            _KeyClear

            ' CONTROL GAME SPEED
            _Limit iFPS
        Wend

        ' UPDATE THE SCREEN
        _Display

        ' CLEAR KEYBOARD BUFFER
        _KeyClear ': _Delay 2

        ' PLAY ANOTHER ROUND OR QUIT?
        If bQuit = FALSE Then
            If bExit = FALSE Then Sleep
            Color cWhite, cBlack
        Else
            Exit Do
        End If
    Loop

    ' RETURN TO AUTODISPLAY
    _AutoDisplay

End Sub ' DrawVectorObjectTest1

' /////////////////////////////////////////////////////////////////////////////
' VECTOR OBJECT DEFINITIONS

' This version stores the vector object definitions in DATA statements
' but we will move back to storing them in an external file.

Sub InitVectorObjects
    Dim RoutineName As String: RoutineName = "InitVectorObjects"
    Dim iLoop As Integer
    Dim iObject As Integer
    Dim iLine As Integer

    Dim x1 As Integer
    Dim y1 As Integer
    Dim x2 As Integer
    Dim y2 As Integer
    Dim r1 As Integer
    Dim g1 As Integer
    Dim b1 As Integer

    iObject = 1
    iLine = 1

    Restore VectorData
    For iLoop = 1 To 1024
        Read x1
        Read y1
        Read x2
        Read y2
        Read r1
        Read g1
        Read b1 ' -255 means no more data, -254 means last set for this object

        If b1 = -255 Then
            ' done with everything, finish last object & exit
            m_arrLines(iObject, iLine).IsLast = TRUE
            m_arrObject(iObject).IsEnabled = TRUE
            Exit For
        ElseIf b1 = -254 Then
            ' done with this object, finish & move to next
            m_arrLines(iObject, iLine).IsLast = TRUE
            m_arrObject(iObject).IsEnabled = TRUE
            iObject = iObject + 1
            iLine = 1

            ' if more data than array, quit
            If iObject > UBound(m_arrLines, 1) Then
                Exit For
            End If
        Else
            ' if more data than array,
            ' just keep reading until either
            ' we get to the next object or time to quit
            If iLine <= UBound(m_arrLines, 2) Then
                m_arrLines(iObject, iLine).x1 = x1
                m_arrLines(iObject, iLine).y1 = y1
                m_arrLines(iObject, iLine).x2 = x2
                m_arrLines(iObject, iLine).y2 = y2
                m_arrLines(iObject, iLine).color = _RGB32(r1, g1, b1)
                m_arrLines(iObject, iLine).IsLast = FALSE
                iLine = iLine + 1
            End If
        End If
    Next iLoop%

    VectorData:

    ' Objects are defined as a collection of line segments, in the form:
    '     Data {x1},{y1},{x2},{y2},{red},{green},{blue}
    ' where
    ' * {x1},{y1} are the starting point of the line
    ' * {x2},{y2} are the ending point of the line
    ' * {red},{green},{blue} are the RGB color of the line segment
    ' * 0,0 is the origin,
    ' * negative numbers mean to the left or above the origin
    ' * positive numbers mean to the right or below the origin
    ' * if the {blue} value is -254 like
    '     Data 0,0,0,0,-254,-254,-254
    '   then that line is not used,
    '   it just exists to tell the parser that object's definition is done,
    ' * if the {blue} value is -255 like
    '     Data 0,0,0,0,-255,-255,-255
    '   then that line is not used
    '   it just exists to tell the parser no more data, stop parsing.

    ' For now we're using data statements, but later might store
    ' these definitions in a separate file that an editor can read/write.

    'objaster1 = purple
    Data 2,-41,31,-50,128,0,255
    Data 31,-50,56,-23,128,0,255
    Data 56,-23,37,-10,128,0,255
    Data 37,-10,61,13,128,0,255
    Data 61,13,32,62,128,0,255
    Data 32,62,-22,43,128,0,255
    Data -22,43,-40,57,128,0,255
    Data -40,57,-62,34,128,0,255
    Data -62,34,-47,7,128,0,255
    Data -47,7,-62,-26,128,0,255
    Data -62,-26,-32,-63,128,0,255
    Data -32,-63,2,-41,128,0,255
    Data 0,0,0,0,-254,-254,-254

    'objaster2 = red
    Data -28,-62,22,-62,255,0,0
    Data 22,-62,61,-28,255,0,0
    Data 61,-28,61,13,255,0,0
    Data 61,13,23,57,255,0,0
    Data 23,57,-6,62,255,0,0
    Data -6,62,-6,15,255,0,0
    Data -6,15,-36,47,255,0,0
    Data -36,47,-59,14,255,0,0
    Data -59,14,-35,1,255,0,0
    Data -35,1,-62,-9,255,0,0
    Data -62,-9,-28,-62,255,0,0
    Data 0,0,0,0,-254,-254,-254

    'objaster3 = orange
    Data 9,-62,60,-21,255,165,0
    Data 60,-21,62,-3,255,165,0
    Data 62,-3,24,13,255,165,0
    Data 24,13,53,34,255,165,0
    Data 53,34,38,55,255,165,0
    Data 38,55,20,40,255,165,0
    Data 20,40,-37,61,255,165,0
    Data -37,61,-63,15,255,165,0
    Data -63,15,-57,-24,255,165,0
    Data -57,-24,-24,-24,255,165,0
    Data -24,-24,-38,-45,255,165,0
    Data -38,-45,9,-62,255,165,0
    Data 0,0,0,0,-254,-254,-254

    'objmouse = yellow
    Data 0,-10,6,3,255,255,0
    Data 6,3,1,2,255,255,0
    Data 1,2,1,10,255,255,0
    Data 1,10,-1,10,255,255,0
    Data -1,10,-1,2,255,255,0
    Data -1,2,-6,3,255,255,0
    Data -6,3,0,-10,255,255,0
    Data 0,0,0,0,-254,-254,-254

    'objship = cyan
    Data 0,-15,10,15,0,255,255
    Data 10,15,6,11,0,255,255
    Data 6,11,-6,11,0,255,255
    Data -6,11,-10,15,0,255,255
    Data -10,15,0,-15,0,255,255
    Data 0,0,0,0,-254,-254,-254

    'objufo = green
    Data -4,-16,4,-16,0,255,0
    Data 4,-16,10,-6,0,255,0
    Data 10,-6,25,5,0,255,0
    Data 25,5,10,16,0,255,0
    Data 10,16,-10,16,0,255,0
    Data -10,16,-25,5,0,255,0
    Data -25,5,-10,-6,0,255,0
    Data -10,-6,-4,-16,0,255,0
    Data -10,-6,10,-6,0,255,0
    Data -25,5,25,5,0,255,0
    Data 0,0,0,0,-255,-255,-255

End Sub ' InitVectorObjects

' /////////////////////////////////////////////////////////////////////////////
' (RE)DRAW SCREEN

Sub DrawLayers (imgBack&, imgMiddle&, imgFront&)
    Dim RoutineName As String: RoutineName = "DrawLayers"

    _Dest 0
    Cls , cBlack

    If TRUE = FALSE Then
        If imgBack& < -1 Then
            _PutImage , imgBack&, 0
        End If

        If imgMiddle& < -1 Then
            _PutImage , imgMiddle&, 0
        End If

        If imgFront& < -1 Then
            _PutImage , imgFront&, 0
        End If
    End If

End Sub ' DrawLayers

' ****************************************************************************************************************************************************************
' END SIMPLE VECTOR ENGINE @VEC
' ****************************************************************************************************************************************************************

' ****************************************************************************************************************************************************************
' BEGIN TEST CODE #TEST
' ****************************************************************************************************************************************************************

' /////////////////////////////////////////////////////////////////////////////

'Sub TestDivideAndRound1
'    Dim mySingle As Single
'    Dim myDouble As Double
'    Dim myFloat1 As _Float
'    Dim in$
'    ' Excel 1/360 = 0.002778
'    mySingle = 1 / 360
'    myDouble = 1 / 360
'    myFloat1 = 1 / 360
'    Print "Single 1/360 = " + _Trim$(Str$(mySingle)) + " or " + SngToStr$(mySingle) + " or " + SngRoundedToStr$(mySingle, 6)
'    Print "Double 1/360 = " + _Trim$(Str$(myDouble)) + " or " + DblToStr$(myDouble) + " or " + DblRoundedToStr$(myDouble, 6)
'    Print "_FLOAT 1/360 = " + _Trim$(Str$(myFloat1)) + " or " + FloatToStr$(myFloat1) + " or " + FloatRoundedToStr$(myFloat1, 6)
'
'    Input "Press <ENTER> to continue", in$
'End Sub ' TestDivideAndRound1

' ****************************************************************************************************************************************************************
' END TEST CODE @TEST
' ****************************************************************************************************************************************************************

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' BEGIN GAME CODE #GAME
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' /////////////////////////////////////////////////////////////////////////////

Sub game ()
    Dim RoutineName As String: RoutineName = "game"

    Screen _NewImage(iMaxX, iMaxY, 32)
    _KeyClear
   
    InitDxDyTables
    InitVariables ' Initialize variables
   
    _KeyClear
   
    Do ' main game loop
        Cls ' Clear the form
        GetInput
        If m_bEscKey = FALSE Then
            MoveBullets ' Activates the MoveBullets sub
            MoveAllShips ' Activates the MoveAllShips sub
            MoveEnemy ' (doesn't do much yet)
            Collisions ' Activates the Collisions sub
        Else
            m_bGameOver = TRUE
        End If
       
        If m_bGameOver = FALSE Then
            Shooting ' Activates the Shooting sub
            DrawEnemy ' Activates the DrawEnemy sub
            DrawBullets ' Activates the DrawBullets sub
            ShowScore ' Display the score, etc.
            DrawAllShips ' Activates the DrawAllShips sub
            Respawn ' Activates the Respawn sub
        Else
            If AskPlayAgain% = TRUE Then
                InitVariables
            Else
                Exit Do
            End If
        End If

        ' UPDATE THE SCREEN
        _Display
       
        ' CONTROL GAME SPEED
        _Limit iFPS
    Loop
   
    ' RETURN TO AUTODISPLAY
    _AutoDisplay
   
End Sub ' game

' /////////////////////////////////////////////////////////////////////////////

Function AskPlayAgain%
    Dim bResult As Integer
    Dim in$

    Cls
    Print "GAME OVER"
    Print
    Print "Level: " + cstr$(m_iLevel)
   
    'TODO: show scores for all players
    'Print "Score: " + cstr$(m_iScore)
    Print
    Do
        Input "Do you wish to try again (y/n) "; in$
        If LCase$(_Trim$(in$)) = "y" Then
            bResult = TRUE
            Exit Do
        ElseIf LCase$(_Trim$(in$)) = "n" Then
            bResult = FALSE
            Exit Do
        Else
            Print
            Print "Please type 'y' or 'n'"
            Print
        End If
    Loop

    AskPlayAgain% = bResult
End Function ' AskPlayAgain%

' /////////////////////////////////////////////////////////////////////////////
' Set the initial state for variables

Sub InitVariables ()
    Dim iLoop1 As Integer ' Used for variables
    Dim iLoop2 As Integer
    Dim iLoop3 As Integer
    Dim iTurn As Integer
    Dim iSpread As Integer
    Dim iHalf As Integer
    Dim iDivisor As Integer
    Dim bDone As Integer
    Dim in$

    ' Ask how many players?
    Cls
    Do
        Input "How many players (1-16, 0 to quit) "; in$
        in$ = _Trim$(in$)
        If Len(in$) < 3 Then
            If IsNumber%(in$) = TRUE Then
                m_iPlayers = Val(in$)
                If m_iPlayers >= 0 And iPlayers <= 16 Then
                    Exit Do
                End If
            End If
        End If
        Print: Print "Please type a number 0-16.": Print
    Loop
   
    ' Initialize game
    If m_iPlayers > 0 Then
        ' Score
        m_iLevel = 1
       
        ' Game status
        m_bGameOver = FALSE
       
        ' Enemy min/max radius
        iSpread = MAX_ENEMY_RADIUS - MIN_ENEMY_RADIUS
        iHalf = iSpread / 2
        iDivisor = iSpread / 10
        m_iMinEnemyRadius = iHalf - iDivisor
        If m_iMinEnemyRadius < MIN_ENEMY_RADIUS Then m_iMinEnemyRadius = MIN_ENEMY_RADIUS
        m_iMaxEnemyRadius = iHalf + iDivisor
        If m_iMaxEnemyRadius > MAX_ENEMY_RADIUS Then m_iMaxEnemyRadius = MAX_ENEMY_RADIUS
       
        'ReDim m_arrShip(m_iPlayers) As ShipType
        ReDim m_arrShip(1 To 16) As ShipType
       
        ' CLEAR CONTROL BUFFER
        For iLoop2 = 1 To m_iPlayers
            ' Clear input flags
            m_arrShip(iLoop2).Left_IsPressed = FALSE
            m_arrShip(iLoop2).Right_IsPressed = FALSE
            m_arrShip(iLoop2).Up_IsPressed = FALSE
            m_arrShip(iLoop2).Down_IsPressed = FALSE
            m_arrShip(iLoop2).Shoot_IsPressed = FALSE
            m_arrShip(iLoop2).Cheat_IsPressed = FALSE
        Next iLoop2
       
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        ' BEGIN INITIALZE PLAYER COLORS
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        ' TODO: allow players to select colors
        m_arrShip(1).BodyColor = cWhite: m_arrShip(1).EngineColor = cWhite: m_arrShip(1).FlameColor = cRed
        m_arrShip(2).BodyColor = cGray: m_arrShip(2).EngineColor = cGray: m_arrShip(2).FlameColor = cOrangeRed
        m_arrShip(3).BodyColor = cDarkOrange: m_arrShip(3).EngineColor = cDarkOrange: m_arrShip(3).FlameColor = cOrange
        m_arrShip(4).BodyColor = cGold: m_arrShip(4).EngineColor = cGold: m_arrShip(4).FlameColor = cWhiteSmoke
        m_arrShip(5).BodyColor = cOliveDrab1: m_arrShip(5).EngineColor = cOliveDrab1: m_arrShip(5).FlameColor = cChartreuse
        m_arrShip(6).BodyColor = cMediumSpringGreen: m_arrShip(6).EngineColor = cMediumSpringGreen: m_arrShip(6).FlameColor = cLime
        m_arrShip(7).BodyColor = cCyan: m_arrShip(7).EngineColor = cCyan: m_arrShip(7).FlameColor = cSpringGreen
        m_arrShip(8).BodyColor = cDodgerBlue: m_arrShip(8).EngineColor = cDodgerBlue: m_arrShip(8).FlameColor = cDeepSkyBlue
        m_arrShip(9).BodyColor = cSeaBlue: m_arrShip(9).EngineColor = cSeaBlue: m_arrShip(9).FlameColor = cBlue
        m_arrShip(10).BodyColor = cDeepPink: m_arrShip(10).EngineColor = cDeepPink: m_arrShip(10).FlameColor = cLightPink
        m_arrShip(11).BodyColor = cMagenta: m_arrShip(11).EngineColor = cMagenta: m_arrShip(11).FlameColor = cHotPink
        m_arrShip(12).BodyColor = cOliveDrab: m_arrShip(12).EngineColor = cOliveDrab: m_arrShip(12).FlameColor = cPurpleRed
        m_arrShip(13).BodyColor = cBluePurple: m_arrShip(13).EngineColor = cBluePurple: m_arrShip(13).FlameColor = cPurple
        m_arrShip(14).BodyColor = cGreen: m_arrShip(14).EngineColor = cGreen: m_arrShip(14).FlameColor = cDeepPurple
        m_arrShip(15).BodyColor = cBrickRed: m_arrShip(15).EngineColor = cBrickRed: m_arrShip(15).FlameColor = cLightGray
        m_arrShip(16).BodyColor = cYellow: m_arrShip(16).EngineColor = cYellow: m_arrShip(16).FlameColor = cSilver
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        ' END INITIALZE PLAYER COLORS
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
       
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        ' BEGIN INITIALZE CONTROL MAPPING
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        ' TODO: allow players to map their own controls (keyboard, gamepad, mouse, etc.)
        m_arrShip(1).Left_KeyCode = KeyCode_Left%
        m_arrShip(1).Right_KeyCode = KeyCode_Right%
        m_arrShip(1).Up_KeyCode = KeyCode_Up%
        m_arrShip(1).Down_KeyCode = KeyCode_Down%
        m_arrShip(1).Shoot_KeyCode = KeyCode_CtrlRight%
        m_arrShip(1).Cheat_KeyCode = KeyCode_Keypad0Ins%
        
        m_arrShip(2).Left_KeyCode = KeyCode_Keypad7Home%
        m_arrShip(2).Right_KeyCode = KeyCode_Keypad8Up%
        m_arrShip(2).Up_KeyCode = KeyCode_Keypad9PgUp%
        m_arrShip(2).Down_KeyCode = KeyCode_Keypad6Right%
        m_arrShip(2).Shoot_KeyCode = KeyCode_Keypad5%
        m_arrShip(2).Cheat_KeyCode = KeyCode_Keypad4Left%
        
        m_arrShip(3).Left_KeyCode = KeyCode_Keypad1End%
        m_arrShip(3).Right_KeyCode = KeyCode_Keypad2Down%
        m_arrShip(3).Up_KeyCode = KeyCode_Keypad3PgDn%
        m_arrShip(3).Down_KeyCode = KeyCode_KeypadPeriodDel%
        m_arrShip(3).Shoot_KeyCode = KeyCode_KeypadEnter%
        m_arrShip(3).Cheat_KeyCode = KeyCode_KeypadPlus%
        
        m_arrShip(4).Left_KeyCode = KeyCode_1%
        m_arrShip(4).Right_KeyCode = KeyCode_2%
        m_arrShip(4).Up_KeyCode = KeyCode_3%
        m_arrShip(4).Down_KeyCode = KeyCode_4%
        m_arrShip(4).Shoot_KeyCode = KeyCode_5%
        m_arrShip(4).Cheat_KeyCode = KeyCode_Tilde%
        
        m_arrShip(5).Left_KeyCode = KeyCode_6%
        m_arrShip(5).Right_KeyCode = KeyCode_7%
        m_arrShip(5).Up_KeyCode = KeyCode_8%
        m_arrShip(5).Down_KeyCode = KeyCode_9%
        m_arrShip(5).Shoot_KeyCode = KeyCode_0%
        m_arrShip(5).Cheat_KeyCode = KeyCode_Minus%
        
        m_arrShip(6).Left_KeyCode = KeyCode_BracketLeft%
        m_arrShip(6).Right_KeyCode = KeyCode_BracketRight%
        m_arrShip(6).Up_KeyCode = KeyCode_BkSp%
        m_arrShip(6).Down_KeyCode = KeyCode_Enter%
        m_arrShip(6).Shoot_KeyCode = KeyCode_Backslash%
        m_arrShip(6).Cheat_KeyCode = KeyCode_Equal%
        
        m_arrShip(7).Left_KeyCode = KeyCode_Ins%
        m_arrShip(7).Right_KeyCode = KeyCode_Home%
        m_arrShip(7).Up_KeyCode = KeyCode_PgDn%
        m_arrShip(7).Down_KeyCode = KeyCode_PgUp%
        m_arrShip(7).Shoot_KeyCode = KeyCode_End%
        m_arrShip(7).Cheat_KeyCode = KeyCode_Del%
        
        m_arrShip(8).Left_KeyCode = KeyCode_Q%
        m_arrShip(8).Right_KeyCode = KeyCode_W%
        m_arrShip(8).Up_KeyCode = KeyCode_E%
        m_arrShip(8).Down_KeyCode = KeyCode_R%
        m_arrShip(8).Shoot_KeyCode = KeyCode_T%
        m_arrShip(8).Cheat_KeyCode = KeyCode_KeypadSlash%
        
        m_arrShip(9).Left_KeyCode = KeyCode_Y%
        m_arrShip(9).Right_KeyCode = KeyCode_U%
        m_arrShip(9).Up_KeyCode = KeyCode_I%
        m_arrShip(9).Down_KeyCode = KeyCode_O%
        m_arrShip(9).Shoot_KeyCode = KeyCode_P%
        m_arrShip(9).Cheat_KeyCode = KeyCode_KeypadMultiply%
        
        m_arrShip(10).Left_KeyCode = KeyCode_A%
        m_arrShip(10).Right_KeyCode = KeyCode_S%
        m_arrShip(10).Up_KeyCode = KeyCode_D%
        m_arrShip(10).Down_KeyCode = KeyCode_F%
        m_arrShip(10).Shoot_KeyCode = KeyCode_G%
        m_arrShip(10).Cheat_KeyCode = KeyCode_KeypadMinus%
        
        m_arrShip(11).Left_KeyCode = KeyCode_Z%
        m_arrShip(11).Right_KeyCode = KeyCode_X%
        m_arrShip(11).Up_KeyCode = KeyCode_C%
        m_arrShip(11).Down_KeyCode = KeyCode_V%
        m_arrShip(11).Shoot_KeyCode = KeyCode_B%
        m_arrShip(11).Cheat_KeyCode = KeyCode_F12%
        
        m_arrShip(12).Left_KeyCode = KeyCode_N%
        m_arrShip(12).Right_KeyCode = KeyCode_M%
        m_arrShip(12).Up_KeyCode = KeyCode_Comma%
        m_arrShip(12).Down_KeyCode = KeyCode_Period%
        m_arrShip(12).Shoot_KeyCode = KeyCode_Slash%
        m_arrShip(12).Cheat_KeyCode = KeyCode_Menu%
        
        m_arrShip(13).Left_KeyCode = KeyCode_H%
        m_arrShip(13).Right_KeyCode = KeyCode_J%
        m_arrShip(13).Up_KeyCode = KeyCode_K%
        m_arrShip(13).Down_KeyCode = KeyCode_L%
        m_arrShip(13).Shoot_KeyCode = KeyCode_Semicolon%
        m_arrShip(13).Cheat_KeyCode = KeyCode_Apostrophe%
        
        m_arrShip(14).Left_KeyCode = KeyCode_F1%
        m_arrShip(14).Right_KeyCode = KeyCode_F2%
        m_arrShip(14).Up_KeyCode = KeyCode_F3%
        m_arrShip(14).Down_KeyCode = KeyCode_F4%
        m_arrShip(14).Shoot_KeyCode = KeyCode_F5%
        m_arrShip(14).Cheat_KeyCode = KeyCode_ScrollLock%
        
        m_arrShip(15).Left_KeyCode = KeyCode_F6%
        m_arrShip(15).Right_KeyCode = KeyCode_F7%
        m_arrShip(15).Up_KeyCode = KeyCode_F8%
        m_arrShip(15).Down_KeyCode = KeyCode_F9%
        m_arrShip(15).Shoot_KeyCode = KeyCode_F11%
        m_arrShip(15).Cheat_KeyCode = KeyCode_NumLock%
        
        m_arrShip(16).Left_KeyCode = KeyCode_Spacebar%
        m_arrShip(16).Right_KeyCode = KeyCode_ShiftRight%
        m_arrShip(16).Up_KeyCode = KeyCode_ShiftLeft%
        m_arrShip(16).Down_KeyCode = KeyCode_CtrlLeft%
        m_arrShip(16).Shoot_KeyCode = KeyCode_Tab%
        m_arrShip(16).Cheat_KeyCode = KeyCode_CapsLock%
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        ' END INITIALZE CONTROL MAPPING
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
       
        ' Set the starting positions of the ships
        For iLoop1 = 1 To m_iPlayers
            ' FOR NOW JUST NAME EACH PLAYER A LETTER OF THE ALPHABET
            m_arrShip(iLoop1).name = Chr$(iLoop1 + 64)
           
            ' RESET SCORE
            m_arrShip(iLoop1).score = 0
            m_arrShip(iLoop1).level = 0
           
            ' EVERYONE STARTS WITH SAME SHIELDS
            ' TODO: GET THIS VALUE FROM OPTIONS
            m_arrShip(iLoop1).shields = 100
           
            ' INITIALIZE SHIP SIZE
            ' TODO: let player pick different shape ships, and calculate radius
            ' TODO: eventually we won't use radius for collision checking (e.g. odd-shaped vessels)
            m_arrShip(iLoop1).radius = SHIP_RADIUS
           
            ' TODO: ADD OPTIONS FOR FUEL AND AMMO, FOR NOW NOT USED...
            m_arrShip(iLoop1).fuel = 100
            If m_iPlayers > 1 Then
                m_arrShip(iLoop1).ammo = (m_iPlayers - 1) * 2 ' 2 shots per opponent
            Else
                m_arrShip(iLoop1).ammo = 10
            End If

            ' PLACE SHIP
            ' TODO: PLACE SHIPS AROUND EDGES OF SCREEN, ON A BASE, ETC.
            bDone = FALSE
            Do
                m_arrShip(iLoop1).xPos = RandomNumber%(iMinX, iMaxX) 'Int(Rnd * ScaleWidth + 1)
                m_arrShip(iLoop1).yPos = RandomNumber%(iMinY, iMaxY) 'Int(Rnd * ScaleHeight + 1)
                For iLoop2 = 1 To m_iPlayers
                    If iLoop2 <> iLoop1 Then
                        ' Is ship too close to another ship?
                        If GetDist(m_arrShip(iLoop1).xPos, m_arrShip(iLoop1).yPos, m_arrShip(iLoop2).xPos, m_arrShip(iLoop2).yPos) > (m_arrShip(iLoop1).radius * 4) Then
                            bDone = TRUE
                            Exit For
                        End If
                    End If
                Next iLoop2
                If bDone = TRUE Then Exit Do
            Loop
           
            ' POINT SHIP
            iTurn = RandomNumber%(0, 143)
            If iTurn = 0 Then
                m_arrShip(iLoop1).facing = 0
                m_arrShip(iLoop1).dx = 0
                m_arrShip(iLoop1).dy = 0
            Else
                For iLoop3 = 1 To iTurn
                    m_arrShip(iLoop1).facing = m_arrShip(iLoop1).facing + PI / TURN_SPEED
                    m_arrShip(iLoop1).dx = m_arrShip(iLoop1).dx + 1
                    If m_arrShip(iLoop1).dx > 143 Then m_arrShip(iLoop1).dx = 0
                    m_arrShip(iLoop1).dy = m_arrShip(iLoop1).dy + 1
                    If m_arrShip(iLoop1).dy > 143 Then m_arrShip(iLoop1).dy = 0
                Next iLoop3
            End If

            ' SHOP NOT MOVING
            ' TODO: MAYBE ADD OPTION TO START PLAYERS DRIFTING IN A RANDOM DIRECTION?
            m_arrShip(iLoop1).dx = 0
            m_arrShip(iLoop1).dy = 0
            m_arrShip(iLoop1).vx = 0
            m_arrShip(iLoop1).vy = 0

            ' VARIABLES THAT LIMIT HOW FAST PLAYER CAN SHOOT
            ' TODO: add option too limit how many shots player can have at a time
            m_arrShip(iLoop1).ShootTime = iFPS \ 4
            m_arrShip(iLoop1).ShootCount = m_arrShip(iLoop1).ShootTime + 1

            ' TODO: REMOVE UNUSED VARIABLES
            m_arrShip(iLoop1).heading = 0
            m_arrShip(iLoop1).facing = 0
            m_arrShip(iLoop1).speed = 0

        Next iLoop1
       
        ' Spawn enemy
        ' TODO: start enemies off screen and let them move in on their own
        ReDim _Preserve m_arrEnemy(0) As EnemyType
        For iLoop1 = 0 To UBound(m_arrEnemy)
            ' Set the starting position of the enemies
            m_arrEnemy(iLoop1).alive = TRUE
            m_arrEnemy(iLoop1).life = 30
            'm_arrEnemy(iLoop1).xPos = m_arrShip(iLoop1).xPos
            'm_arrEnemy(iLoop1).yPos = m_arrShip(iLoop1).yPos
           
            ' choose a random size
            m_arrEnemy(iLoop1).radius = RandomNumber%(m_iMinEnemyRadius, m_iMaxEnemyRadius)
           
            ' Stops the enemy starting on top of any players
            Do
                ' Place enemy randomly
                m_arrEnemy(iLoop1).xPos = RandomNumber%(iMinX, iMaxX) 'Int(Rnd * ScaleWidth + 1)
                m_arrEnemy(iLoop1).yPos = RandomNumber%(iMinY, iMaxY) 'Int(Rnd * ScaleHeight + 1)
               
                ' Make sure enemy is not too close to any players
                bDone = TRUE
                For iLoop2 = 1 To m_iPlayers
                    If GetDist(m_arrEnemy(iLoop1).xPos, m_arrEnemy(iLoop1).yPos, m_arrShip(iLoop2).xPos, m_arrShip(iLoop2).yPos) <= (m_arrShip(iLoop2).radius * 4) Then
                        bDone = FALSE
                        Exit For
                    End If
                Next iLoop2
               
                ' Have we found a place for this enemy?
                If bDone = TRUE Then Exit Do
            Loop
        Next iLoop1
       
        ' RESET BULLETS
        ReDim _Preserve m_arrBullet(-1) As BulletType
    End If
   
End Sub ' InitVariables


' /////////////////////////////////////////////////////////////////////////////
' Detect which keys are pressed

Sub GetInput ()
    Dim iLoop2 As Integer
   
    While _DeviceInput(1): Wend ' clear and update the keyboard buffer
   
    If _Button(KeyCode_Escape%) Then
        m_bEscKey = TRUE
    Else
        m_bEscKey = FALSE
    End If
   
    For iLoop2 = 1 To m_iPlayers
        If m_arrShip(iLoop2).shields > 0 Then
            If _Button(m_arrShip(iLoop2).Left_KeyCode) Then
                m_arrShip(iLoop2).Left_IsPressed = TRUE
                m_arrShip(iLoop2).Right_IsPressed = FALSE
            ElseIf _Button(m_arrShip(iLoop2).Right_KeyCode) Then
                m_arrShip(iLoop2).Left_IsPressed = FALSE
                m_arrShip(iLoop2).Right_IsPressed = TRUE
            Else
                m_arrShip(iLoop2).Left_IsPressed = FALSE
                m_arrShip(iLoop2).Right_IsPressed = FALSE
            End If
           
            If _Button(m_arrShip(iLoop2).Up_KeyCode) Then
                m_arrShip(iLoop2).Up_IsPressed = TRUE
                m_arrShip(iLoop2).Down_IsPressed = FALSE
            ElseIf _Button(m_arrShip(iLoop2).Down_KeyCode) Then
                m_arrShip(iLoop2).Up_IsPressed = FALSE
                m_arrShip(iLoop2).Down_IsPressed = TRUE
            Else
                m_arrShip(iLoop2).Up_IsPressed = FALSE
                m_arrShip(iLoop2).Down_IsPressed = FALSE
            End If

            If _Button(m_arrShip(iLoop2).Shoot_KeyCode) Then
                m_arrShip(iLoop2).Shoot_IsPressed = TRUE
            Else
                m_arrShip(iLoop2).Shoot_IsPressed = FALSE
            End If

            If _Button(m_arrShip(iLoop2).Cheat_KeyCode) Then
                m_arrShip(iLoop2).Cheat_IsPressed = TRUE
            Else
                m_arrShip(iLoop2).Cheat_IsPressed = FALSE
            End If
        End If
    Next iLoop2

    ' CLEAR KEYBOARD BUFFER
    _KeyClear

End Sub ' GetInput

' /////////////////////////////////////////////////////////////////////////////
' Check for collisions

' TODO: improve collision checking to handle different shape polygons, etc.

Sub Collisions ()
    Dim iLoop1 As Integer
    Dim iLoop2 As Integer
    Dim iCount As Integer
    Dim in$
   
    ' Check for bullet collisions
    For iLoop1 = 0 To UBound(m_arrBullet)
       
        ' IS THIS BULLET ALIVE?
        If m_arrBullet(iLoop1).alive = TRUE Then
           
            ' CHECK FOR BULLET HIT BULLET
            For iLoop2 = 0 To UBound(m_arrBullet)
                If iLoop2 <> iLoop1 Then
                    If BULLETS_STOP_BULLETS = TRUE Then
                        If GetDist(m_arrBullet(iLoop2).xPos, m_arrBullet(iLoop2).yPos, m_arrBullet(iLoop1).xPos, m_arrBullet(iLoop1).yPos) <= BULLET_RADIUS Then
                            ' BOTH SHOTS DESTROYED
                            m_arrBullet(iLoop1).alive = FALSE ' Destroy the bullet
                            m_arrBullet(iLoop2).alive = FALSE ' Destroy the other bullet
                        End If
                    End If
                End If
            Next iLoop2
        End If
       
        ' IS THIS BULLET STILL ALIVE?
        If m_arrBullet(iLoop1).alive = TRUE Then
           
            ' Check for collision between bullet and ships
            For iLoop2 = 1 To m_iPlayers
                If GetDist(m_arrShip(iLoop2).xPos, m_arrShip(iLoop2).yPos, m_arrBullet(iLoop1).xPos, m_arrBullet(iLoop1).yPos) <= m_arrShip(iLoop2).radius Then
                   
                    ' ONLY HIT SHIP IF IT'S ALIVE!
                    If m_arrShip(iLoop2).shields > 0 Then
                        ' TODO: DIFFERENT DAMAGE DEPENDING ON WHETHER IT'S A PLAYER OR ENEMY BULLET, WEAPON TYPE, ETC.
                        m_arrShip(iLoop2).shields = m_arrShip(iLoop2).shields - BULLET_DAMAGE ' Take Damage
                        m_arrBullet(iLoop1).alive = FALSE ' Destroy the bullet
                       
                        ' SCORING
                        If m_arrBullet(iLoop1).kind = "SHIP" Then
                            ' PLAYER SCORES IF OPPONENT KILLED
                            ' TODO: award points for hit but not kill?
                            If m_arrShip(iLoop2).shields < 1 Then
                                m_arrShip(m_arrBullet(iLoop1).owner).score = m_arrShip(m_arrBullet(iLoop1).owner).score + 100
                                Exit For
                            End If
                        End If
                    End If
                   
                End If
            Next iLoop2
           
            ' Check for collision between bullet and enemies
            For iLoop2 = 0 To UBound(m_arrEnemy)
               
                ' If the enemy is alive then
                If m_arrEnemy(iLoop2).alive = TRUE Then
                    ' Check for collision between bullet and enemy
                    If GetDist(m_arrEnemy(iLoop2).xPos, m_arrEnemy(iLoop2).yPos, m_arrBullet(iLoop1).xPos, m_arrBullet(iLoop1).yPos) <= m_arrEnemy(iLoop2).radius Then
                       
                        ' TODO: DIFFERENT DAMAGE DEPENDING ON WHETHER IT'S A PLAYER OR ENEMY BULLET, WEAPON TYPE, ETC.
                        m_arrEnemy(iLoop2).life = m_arrEnemy(iLoop2).life - BULLET_DAMAGE ' Enemy take damage
                        m_arrBullet(iLoop1).alive = FALSE ' Destroy the bullet
                       
                        ' if the enemy is dead then destroy it, add to score
                        If m_arrEnemy(iLoop2).life <= 0 Then
                            m_arrEnemy(iLoop2).alive = FALSE
                           
                            ' SCORING
                            If m_arrBullet(iLoop1).kind = "SHIP" Then
                                m_arrShip(m_arrBullet(iLoop1).owner).score = m_arrShip(m_arrBullet(iLoop1).owner).score + 10
                                'm_iScore = m_iScore + 10
                            End If
                        End If
                       
                    End If
                End If
            Next iLoop2
           
        End If
    Next iLoop1
   
    ' CHECK FOR SHIP COLLIDING WITH ENEMY
    For iLoop1 = 0 To UBound(m_arrEnemy)
        ' If the enemy is alive then
        If m_arrEnemy(iLoop1).alive = TRUE Then
            ' Check for collision between ships and enemy
            For iLoop2 = 1 To m_iPlayers
                If GetDist(m_arrShip(iLoop2).xPos, m_arrShip(iLoop2).yPos, m_arrEnemy(iLoop1).xPos, m_arrEnemy(iLoop1).yPos) <= m_arrEnemy(iLoop1).radius Then
                    m_arrEnemy(iLoop1).life = 0 ' The enemy has no life/Dead
                    m_arrShip(iLoop2).shields = 0 ' The ship has no shields/Dead
                   
                    ' if the enemy is dead then destroy it, add to score
                    If m_arrEnemy(iLoop1).life <= 0 Then
                        m_arrEnemy(iLoop1).alive = FALSE
                        'm_iScore = m_iScore + 10
                        m_arrShip(iLoop2).score = m_arrShip(iLoop2).score + 10
                    End If
                   
                    Exit For
                End If
            Next iLoop2
        End If
    Next iLoop1
   
    ' CHECK FOR SHIP COLLIDING WITH SHIP
    For iLoop1 = 1 To m_iPlayers
        ' ONLY CHECK SHIP IF IT'S ALIVE!
        If m_arrShip(iLoop1).shields > 0 Then
            ' CHECK FOR SHIPS COLLIDED WITH
            For iLoop2 = 1 To m_iPlayers
                ' SHIP CAN'T COLLIDE WITH SELF
                If iLoop1 <> iLoop2 Then
                    ' ONLY CHECK SHIP IF IT'S ALIVE!
                    If m_arrShip(iLoop2).shields > 0 Then
                        ' DID THEY COLLIDE?
                        If GetDist(m_arrShip(iLoop1).xPos, m_arrShip(iLoop1).yPos, m_arrShip(iLoop2).xPos, m_arrShip(iLoop2).yPos) <= m_arrShip(iLoop1).radius Then
                           
                            ' DEAD!
                            ' TODO: MAYBE SHIPS DON'T JUST BLOW UP WHEN THEY COLLIDE, ADD OPTIONS FOR DAMAGE, BOUNCE OFF EACH OTHER, ETC.
                            m_arrShip(iLoop1).shields = 0
                            m_arrShip(iLoop2).shields = 0
                           
                            ' SCORE ONE LAST TIME!
                            m_arrShip(iLoop1).score = m_arrShip(iLoop1).score + 100
                            m_arrShip(iLoop2).score = m_arrShip(iLoop2).score + 100
                           
                        End If
                    End If
                End If
            Next iLoop2
        End If
    Next iLoop1
   
    ' HOW MANY PLAYERS STILL ALIVE?
    iCount = 0
    For iLoop2 = 1 To m_iPlayers
        If m_arrShip(iLoop2).shields > 0 Then
            iCount = iCount + 1
        End If
    Next iLoop2
   
    ' GAME OVER?
    If m_iPlayers = 1 Then
        ' SINGLE PLAYER GAME
        If iCount < 1 Then
            m_bGameOver = TRUE
        End If
    Else
        If iCount < 2 Then
            ' MULTIPLAYER GAME
           
            ' TODO: ADD OPTION TO ADVANCE TO NEXT ROUND FOR MULTIPLAYER GAME
            m_bGameOver = TRUE
        End If
    End If
   
End Sub ' Collisions

' /////////////////////////////////////////////////////////////////////////////

Sub Shooting ()
    Dim iLoop1 As Integer ' Used for variables
    Dim iLoop2 As Integer ' Used for variables
    Dim iFreeSpot As Integer
    Dim sngXComp As Single
    Dim sngYComp As Single
    Dim sngCheckDistance As Single
    Dim sngShortestDistance As Single
    Dim iClosestPlayer As Integer
   
    ' DID PLAYER SHOOT?
    For iLoop2 = 1 To m_iPlayers
        'If m_bShootKey = TRUE Then
        If m_arrShip(iLoop2).Shoot_IsPressed = TRUE Then
            ' Has the gun cooled down yet (prevent bullet being created every 25 milliseconds)
           
            If m_arrShip(iLoop2).ShootCount > m_arrShip(iLoop2).ShootTime Then
                m_arrShip(iLoop2).ShootCount = 0
               
                iFreeSpot = -1
               
                For iLoop1 = 0 To UBound(m_arrBullet)
                    ' Check whether it can use another bullet or not
                    If m_arrBullet(iLoop1).alive = FALSE Then
                        ' if so use the dead bullet
                        iFreeSpot = iLoop1
                        Exit For
                    End If
                Next iLoop1
                ' if there were no already dead bullets
                If iFreeSpot = -1 Then
                    ' create another one
                    ReDim _Preserve m_arrBullet(UBound(m_arrBullet) + 1) As BulletType

                    ' iFreeSpot is this new bullet
                    iFreeSpot = UBound(m_arrBullet)
                End If
               
                ' Set the properties of this bullet
                m_arrBullet(iFreeSpot).owner = iLoop2 ' identify player that fired shot
                m_arrBullet(iFreeSpot).alive = TRUE ' The bullet is alive
                m_arrBullet(iFreeSpot).xPos = m_arrShip(iLoop2).xPos ' the bullet is created where the ship is
                m_arrBullet(iFreeSpot).yPos = m_arrShip(iLoop2).yPos ' the bullet is created where the ship is
                m_arrBullet(iFreeSpot).kind = "SHIP" ' This is a Ship Bullet
                m_arrBullet(iFreeSpot).lifespan = BULLET_LIFESPAN * iFPS ' # seconds bullet is alive
                m_arrBullet(iFreeSpot).lifetime = 0 ' bullet is brand new
               
                ' Determine the X and Y components of the resultant vector
                sngXComp = m_arrShip(iLoop2).speed * Sin(m_arrShip(iLoop2).heading) + BULLET_SPEED * Sin(m_arrShip(iLoop2).facing)
                sngYComp = m_arrShip(iLoop2).speed * Cos(m_arrShip(iLoop2).heading) + BULLET_SPEED * Cos(m_arrShip(iLoop2).facing)
               
                ' Determine the resultant speed
                m_arrBullet(iFreeSpot).speed = Sqr(sngXComp ^ 2 + sngYComp ^ 2)
               
                'Calculate the resultant heading, and adjust for arctangent by adding Pi if necessary
                If Sgn(sngYComp) > 0 Then
                    m_arrBullet(iFreeSpot).heading = Atn(sngXComp / sngYComp)
                End If
                If Sgn(sngYComp) < 0 Then
                    m_arrBullet(iFreeSpot).heading = Atn(sngXComp / sngYComp) + PI
                End If
               
                ' PLACE BULLET OUTSIDE OF SHIP
                m_arrBullet(iFreeSpot).xPos = m_arrBullet(iFreeSpot).xPos + ((m_arrShip(iLoop2).radius + 1) * Sin(m_arrBullet(iLoop1).heading))
                m_arrBullet(iFreeSpot).yPos = m_arrBullet(iFreeSpot).yPos - ((m_arrShip(iLoop2).radius + 1) * Cos(m_arrBullet(iLoop1).heading))
               
               
            End If
        End If
    Next iLoop2
   
    ' ENEMIES SHOOT
    For iLoop1 = 0 To UBound(m_arrEnemy)
        ' Check whether the enemy is alive
        If m_arrEnemy(iLoop1).alive = TRUE Then
            ' Check whether the enemy will fire or not
            If Int(Rnd * 100 + 1) = 1 Then
                iFreeSpot = -1
               
                For iLoop2 = 0 To UBound(m_arrBullet)
                    ' Check whether the enemy will use an old bullet
                    If m_arrBullet(iLoop2).alive = FALSE Then
                        ' If so iFreeSpot is the old bullet
                        iFreeSpot = iLoop2
                        Exit For
                    End If
                Next iLoop2
               
                ' If there were no free spots then create another bullet
                If iFreeSpot = -1 Then
                    ' Create the new bullet
                    ReDim _Preserve m_arrBullet(UBound(m_arrBullet) + 1) As BulletType
                    ' iFreeSpot is this new bullet
                    iFreeSpot = UBound(m_arrBullet)
                End If
               
                ' Determine who enemy shoots at
                sngShortestDistance = iMaxX + iMaxY + 1
                iClosestPlayer = 0
                For iLoop2 = 1 To m_iPlayers
                    If m_arrShip(iLoop2).shields > 0 Then
                        sngCheckDistance = GetDist(m_arrEnemy(iLoop1).xPos, m_arrEnemy(iLoop1).yPos, m_arrShip(iLoop2).xPos, m_arrShip(iLoop2).yPos)
                        If sngCheckDistance < sngShortestDistance Then
                            sngShortestDistance = sngCheckDistance
                            iClosestPlayer = iLoop2
                        End If
                    End If
                Next iLoop2
               
                ' TODO: get this working
                If iClosestPlayer > 0 Then
                    ' Set the properties for this bullet
                    m_arrBullet(iFreeSpot).alive = TRUE ' It is Alive!!!
                   
                    ' Set it so the bullet shoots at the ship
                    m_arrBullet(iFreeSpot).owner = iLoop1 ' identify enemy that fired shot
                   
                    ' Aim the shot at the player
                    m_arrBullet(iFreeSpot).heading = GetAngle(m_arrEnemy(iLoop1).xPos, m_arrEnemy(iLoop1).yPos, m_arrShip(iClosestPlayer).xPos, m_arrShip(iClosestPlayer).yPos)
                   
                    m_arrBullet(iFreeSpot).xPos = m_arrEnemy(iLoop1).xPos ' Create the bullet where the enemy is
                    m_arrBullet(iFreeSpot).yPos = m_arrEnemy(iLoop1).yPos ' Create the bullet where the enemy is
                    m_arrBullet(iFreeSpot).speed = 6 ' Set the bullet speed
                    m_arrBullet(iFreeSpot).kind = "ENEMY" ' This is an enemy bullet
                    m_arrBullet(iFreeSpot).lifespan = BULLET_LIFESPAN * iFPS ' # seconds bullet is alive
                    m_arrBullet(iFreeSpot).lifetime = 0 ' bullet is brand new
                   
                    ' Move bullet outside of enemy
                    m_arrBullet(iFreeSpot).xPos = m_arrBullet(iFreeSpot).xPos + ((m_arrEnemy(iLoop1).radius + 1) * Sin(m_arrBullet(iFreeSpot).heading))
                    m_arrBullet(iFreeSpot).yPos = m_arrBullet(iFreeSpot).yPos - ((m_arrEnemy(iLoop1).radius + 1) * Cos(m_arrBullet(iFreeSpot).heading))
                End If
               
            End If
        End If
    Next iLoop1
   
End Sub ' Shooting

' /////////////////////////////////////////////////////////////////////////////
' Draw the enemies

Sub DrawEnemy ()
    Dim iLoop1 As Integer ' Used for variables
    Dim iLoop2 As Integer ' Used for variables
    Dim iColor As _Unsigned Long
    Dim iX As Integer
    Dim iY As Integer
    Dim sngHeading As Single
    Dim iRadius As Integer
    Dim sngCheckDistance As Single
    Dim sngShortestDistance As Single
    Dim iClosestPlayer As Integer
   
    For iLoop1 = 0 To UBound(m_arrEnemy)
        ' Is this enemy alive
        If m_arrEnemy(iLoop1).alive = TRUE Then
            ' Color based on damage
            If m_arrEnemy(iLoop1).life >= 30 Then
                iColor = cWhite
            ElseIf m_arrEnemy(iLoop1).life > 25 Then
                iColor = cYellow
            ElseIf m_arrEnemy(iLoop1).life > 20 Then
                iColor = cGold
            ElseIf m_arrEnemy(iLoop1).life > 15 Then
                iColor = cOrange
            ElseIf m_arrEnemy(iLoop1).life > 10 Then
                iColor = cDarkOrange
            ElseIf m_arrEnemy(iLoop1).life > 5 Then
                iColor = cOrangeRed
            Else
                iColor = cRed
            End If
           
            ' Draw body
            ' CIRCLE (x, y), radius, color
            'DrawCircleSolid iX, iY, 8, cRed
            Circle (m_arrEnemy(iLoop1).xPos, m_arrEnemy(iLoop1).yPos), m_arrEnemy(iLoop1).radius, iColor
           
            ' Determine closest player
            sngShortestDistance = iMaxX + iMaxY + 1
            iClosestPlayer = 0
            For iLoop2 = 1 To m_iPlayers
                If m_arrShip(iLoop2).shields > 0 Then
                    sngCheckDistance = GetDist(m_arrEnemy(iLoop1).xPos, m_arrEnemy(iLoop1).yPos, m_arrShip(iLoop2).xPos, m_arrShip(iLoop2).yPos)
                    If sngCheckDistance < sngShortestDistance Then
                        sngShortestDistance = sngCheckDistance
                        iClosestPlayer = iLoop2
                    End If
                End If
            Next iLoop2
           
            ' Draw "eye"
            ' TODO: get this working
            If iClosestPlayer > 0 Then
                sngHeading = GetAngle(m_arrEnemy(iLoop1).xPos, m_arrEnemy(iLoop1).yPos, m_arrShip(iClosestPlayer).xPos, m_arrShip(iClosestPlayer).yPos)
                iX = m_arrEnemy(iLoop1).xPos
                iY = m_arrEnemy(iLoop1).yPos
                iRadius = m_arrEnemy(iLoop1).radius / 3
                iX = iX + (m_arrEnemy(iLoop1).radius - iRadius) * Sin(sngHeading)
                iY = iY - (m_arrEnemy(iLoop1).radius - iRadius) * Cos(sngHeading)
                Circle (iX, iY), iRadius, iColor
            End If
           
        End If
    Next iLoop1
End Sub ' DrawEnemy

' /////////////////////////////////////////////////////////////////////////////
' Move Bullets

Sub MoveBullets ()
    Dim iLoop1 As Integer ' Used for variables
    Dim iLoop2 As Integer ' Used for variables
   
    For iLoop2 = 1 To m_iPlayers
        If m_arrShip(iLoop2).ShootCount <= m_arrShip(iLoop2).ShootTime Then
            m_arrShip(iLoop2).ShootCount = m_arrShip(iLoop2).ShootCount + 1
        End If
    Next iLoop2
   
    For iLoop1 = 0 To UBound(m_arrBullet)
        ' Is the bullet alive
        If m_arrBullet(iLoop1).alive = TRUE Then
            ' Move the bullets
            m_arrBullet(iLoop1).xPos = m_arrBullet(iLoop1).xPos + (m_arrBullet(iLoop1).speed * Sin(m_arrBullet(iLoop1).heading))
            m_arrBullet(iLoop1).yPos = m_arrBullet(iLoop1).yPos - (m_arrBullet(iLoop1).speed * Cos(m_arrBullet(iLoop1).heading))
           
            ' Did the bullet move off screen horizontally?
            If m_arrBullet(iLoop1).xPos < iMinX Then
                If WRAP_BULLET = TRUE Then
                    m_arrBullet(iLoop1).xPos = iMaxX
                Else
                    m_arrBullet(iLoop1).alive = FALSE ' Destroy the bullet
                End If
            ElseIf m_arrBullet(iLoop1).xPos > iMaxX Then
                If WRAP_BULLET = TRUE Then
                    m_arrBullet(iLoop1).xPos = iMinX
                Else
                    m_arrBullet(iLoop1).alive = FALSE ' Destroy the bullet
                End If
            End If
           
            ' Did the bullet move off screen vertically?
            If m_arrBullet(iLoop1).yPos < iMinY Then
                If WRAP_BULLET = TRUE Then
                    m_arrBullet(iLoop1).yPos = iMaxY
                Else
                    m_arrBullet(iLoop1).alive = FALSE ' Destroy the bullet
                End If
            ElseIf m_arrBullet(iLoop1).yPos > iMaxY Then
                If WRAP_BULLET = TRUE Then
                    m_arrBullet(iLoop1).yPos = iMinY
                Else
                    m_arrBullet(iLoop1).alive = FALSE ' Destroy the bullet
                End If
            End If
           
            ' Time how long bullet stays active
            m_arrBullet(iLoop1).lifetime = m_arrBullet(iLoop1).lifetime + 1
            If m_arrBullet(iLoop1).lifetime > m_arrBullet(iLoop1).lifespan Then
                m_arrBullet(iLoop1).alive = FALSE ' Destroy the bullet
            End If
           
        End If
    Next iLoop1
End Sub ' MoveBullets

' /////////////////////////////////////////////////////////////////////////////
' Draw the bullets

Sub DrawBullets ()
    Dim iLoop1 As Integer ' Used for variables
    For iLoop1 = 0 To UBound(m_arrBullet)
        ' Is the bullet alive
        If m_arrBullet(iLoop1).alive = TRUE Then
            If m_arrBullet(iLoop1).kind = "SHIP" Then
                ' Is this a ship bullet, draw a white bullet
                'Circle (m_arrBullet(iLoop1).xPos, m_arrBullet(iLoop1).yPos), 3, cWhite
                DrawCircleSolid m_arrBullet(iLoop1).xPos, m_arrBullet(iLoop1).yPos, BULLET_RADIUS, cWhite

            ElseIf m_arrBullet(iLoop1).kind = "ENEMY" Then
                ' if this is enemy bullet, draw a red bullet
                'Circle (m_arrBullet(iLoop1).xPos, m_arrBullet(iLoop1).yPos), 3, cOrangeRed
                DrawCircleSolid m_arrBullet(iLoop1).xPos, m_arrBullet(iLoop1).yPos, BULLET_RADIUS, cOrangeRed
            End If
        End If
    Next iLoop1
End Sub ' DrawBullets

' /////////////////////////////////////////////////////////////////////////////

Sub MoveAllShips ()
    Dim iLoop2 As Integer
    For iLoop2 = 1 To m_iPlayers
        If m_arrShip(iLoop2).shields > 0 Then
            MoveShip iLoop2
        End If
    Next iLoop2
End Sub ' MoveAllShips

' /////////////////////////////////////////////////////////////////////////////
' Move the ship
Sub MoveShip (iShipIndex As Integer)
    Dim sngXComp As Single
    Dim sngYComp As Single
   
    ' If the left key is pressed then rotate the ship left
    If m_arrShip(iShipIndex).Left_IsPressed = TRUE Then
        m_arrShip(iShipIndex).facing = m_arrShip(iShipIndex).facing - PI / TURN_SPEED
        m_arrShip(iShipIndex).dx = m_arrShip(iShipIndex).dx - 1
        If m_arrShip(iShipIndex).dx < 0 Then m_arrShip(iShipIndex).dx = 143
        m_arrShip(iShipIndex).dy = m_arrShip(iShipIndex).dy - 1
        If m_arrShip(iShipIndex).dy < 0 Then m_arrShip(iShipIndex).dy = 143
    End If
   
    ' If the Right key is pressed then rotate the ship right
    If m_arrShip(iShipIndex).Right_IsPressed = TRUE Then
        m_arrShip(iShipIndex).facing = m_arrShip(iShipIndex).facing + PI / TURN_SPEED
        m_arrShip(iShipIndex).dx = m_arrShip(iShipIndex).dx + 1
        If m_arrShip(iShipIndex).dx > 143 Then m_arrShip(iShipIndex).dx = 0
        m_arrShip(iShipIndex).dy = m_arrShip(iShipIndex).dy + 1
        If m_arrShip(iShipIndex).dy > 143 Then m_arrShip(iShipIndex).dy = 0
    End If
   
    ' If the up key is pressed then and accelerate it in the direction the ship is facing
    If m_arrShip(iShipIndex).Up_IsPressed = TRUE Then
        ' ****************************************************************************************************************************************************************
        ' ****************************************************************************************************************************************************************
        ' ****************************************************************************************************************************************************************
        ' TODO: fix this to make the movement more natural...
       
        ' ACCELERATE X AND Y DIRECTIONS BASED ON WHICH DIRECTION WE'RE POINTING
        m_arrShip(iShipIndex).vx = m_arrShip(iShipIndex).vx + (m_arrDX(m_arrShip(iShipIndex).dx) * SHIP_ACCEL)
        m_arrShip(iShipIndex).vy = m_arrShip(iShipIndex).vy + (m_arrDY(m_arrShip(iShipIndex).dy) * SHIP_ACCEL)
       
       
        If TRUE = FALSE Then
            ' Determine the X and Y components of the resultant vector
            sngXComp = m_arrShip(iShipIndex).speed * Sin(m_arrShip(iShipIndex).heading) + SHIP_ACCEL * Sin(m_arrShip(iShipIndex).facing)
            sngYComp = m_arrShip(iShipIndex).speed * Cos(m_arrShip(iShipIndex).heading) + SHIP_ACCEL * Cos(m_arrShip(iShipIndex).facing)
       
            ' Determine the resultant speed
            m_arrShip(iShipIndex).speed = Sqr(sngXComp ^ 2 + sngYComp ^ 2)
       
            ' Calculate the resultant heading, and adjust for arctangent by adding Pi if necessary
            If Sgn(sngYComp) > 0 Then
                m_arrShip(iShipIndex).heading = Atn(sngXComp / sngYComp)
            End If
            If Sgn(sngYComp) < 0 Then
                m_arrShip(iShipIndex).heading = Atn(sngXComp / sngYComp) + PI
            End If
        End If
       
        ' ****************************************************************************************************************************************************************
        ' ****************************************************************************************************************************************************************
        ' ****************************************************************************************************************************************************************
    End If
   
    ' If the down key is pressed then and accelerate the ship in the opposite direction it is facing
    If TRUE = FALSE Then
        If m_arrShip(iShipIndex).Down_IsPressed = TRUE And m_arrShip(iShipIndex).speed > -MAX_SPEED Then
            ' Determine the X and Y components of the resultant vector
            sngXComp = m_arrShip(iShipIndex).speed * Sin(m_arrShip(iShipIndex).heading) - SHIP_ACCEL * Sin(m_arrShip(iShipIndex).facing)
            sngYComp = m_arrShip(iShipIndex).speed * Cos(m_arrShip(iShipIndex).heading) - SHIP_ACCEL * Cos(m_arrShip(iShipIndex).facing)
       
            ' Determine the resultant speed
            m_arrShip(iShipIndex).speed = Sqr(sngXComp ^ 2 + sngYComp ^ 2)
       
            ' Calculate the resultant heading, and adjust for actangent by adding Pi if necessary
            If Sgn(sngYComp) > 0 Then
                m_arrShip(iShipIndex).heading = Atn(sngXComp / sngYComp)
            End If
            If Sgn(sngYComp) < 0 Then
                m_arrShip(iShipIndex).heading = Atn(sngXComp / sngYComp) + PI
            End If
        End If
    End If
   
    ' Did player hit cheat key?
    If m_arrShip(iShipIndex).Cheat_IsPressed = TRUE Then
        m_arrShip(iShipIndex).shields = m_arrShip(iShipIndex).shields + 10
    End If
   
    ' Don't let the ship go faster then the max speed
    If m_arrShip(iShipIndex).vx < 0 - MAX_SPEED Then
        m_arrShip(iShipIndex).vx = 0 - MAX_SPEED
    ElseIf m_arrShip(iShipIndex).vx > MAX_SPEED Then
        m_arrShip(iShipIndex).vx = MAX_SPEED
    End If
    If m_arrShip(iShipIndex).vy < 0 - MAX_SPEED Then
        m_arrShip(iShipIndex).vy = 0 - MAX_SPEED
    ElseIf m_arrShip(iShipIndex).vy > MAX_SPEED Then
        m_arrShip(iShipIndex).vy = MAX_SPEED
    End If

    If TRUE = FALSE Then
        If m_arrShip(iShipIndex).speed > MAX_SPEED Then
            m_arrShip(iShipIndex).speed = MAX_SPEED
        End If
    End If
   
    ' Move the ship
    'm_arrShip(iShipIndex).xPos = m_arrShip(iShipIndex).xPos + m_arrShip(iShipIndex).speed * Sin(m_arrShip(iShipIndex).heading)
    'm_arrShip(iShipIndex).yPos = m_arrShip(iShipIndex).yPos - m_arrShip(iShipIndex).speed * Cos(m_arrShip(iShipIndex).heading)
    m_arrShip(iShipIndex).xPos = m_arrShip(iShipIndex).xPos + m_arrShip(iShipIndex).vx
    m_arrShip(iShipIndex).yPos = m_arrShip(iShipIndex).yPos + m_arrShip(iShipIndex).vy
   
    ' Keep the ship inside the form
    If m_arrShip(iShipIndex).xPos < iMinX Then
        m_arrShip(iShipIndex).xPos = iMaxX
    End If
    If m_arrShip(iShipIndex).xPos > iMaxX Then
        m_arrShip(iShipIndex).xPos = iMinX
    End If
    If m_arrShip(iShipIndex).yPos < iMinY Then
        m_arrShip(iShipIndex).yPos = iMaxY
    End If
    If m_arrShip(iShipIndex).yPos > iMaxY Then
        m_arrShip(iShipIndex).yPos = iMinY
    End If
End Sub ' MoveShip

' /////////////////////////////////////////////////////////////////////////////
' Placeholder

Sub MoveEnemy ()
    Dim iLoop1 As Integer
    'For iLoop1 = 0 To UBound(m_arrEnemy)
    '    ' Check whether the enemy is alive
    '    If m_arrEnemy(iLoop1).alive = TRUE Then
    '    End If
    'Next iLoop1
End Sub ' MoveEnemy

' /////////////////////////////////////////////////////////////////////////////

Sub ShowScore
    Dim iLoop2 As Integer
    Dim iRow As Integer
    Dim iCol As Integer
   
    ' Draw background
    Color cBlue, cBlue
    PrintAt 0, 0, String$(120, " ")
   
    ' Place the text on the form
   
    'TODO: display score for all players
    'Color cLime, cEmpty
    'PrintAt 0, 10, "Shields: " + cstr$(m_arrShip(iLoop1).shields) ' LeftPadString$(cstr$(m_arrShip(iLoop1).shields), 5, " ")
   
    ' Title displays the players score
    'TODO: display score for all players
    'Color cCyan, cEmpty
    'PrintAt 0, 40, "Score: " + cstr$(m_iScore) ' LeftPadString$(cstr$(m_iScore), 10, " ")
   
    ' Display the level
    Color cWhite, cEmpty
    PrintAt 0, 70, "Level: " + cstr$(m_iLevel) ' LeftPadString$(cstr$(m_iScore), 10, " ")
   
   
    iRow = 2
    iCol = 2
    For iLoop2 = 1 To m_iPlayers
        'if m_arrShip(iLoop2).shields > 0 then
        Color m_arrShip(iLoop2).BodyColor, cEmpty
            PrintAt iRow, iCol, _
                "Player #" + _
                LeftPadString$(cstr$(iLoop2), 2, " ") + _
                " (" + m_arrShip(iLoop2).name + ") : " + _
                "Shields: " + LeftPadString$(cstr$(m_arrShip(iLoop2).shields), 5, " ") + "  " + _
                "Score: " + LeftPadString$(cstr$(m_arrShip(iLoop2).score), 5, " ") + "  " + _
                "x: " + LeftPadString$(_Trim$(Str$(m_arrShip(iLoop2).xPos)), 5, " ") + "  " + _
                "y: " + LeftPadString$(_Trim$(Str$(m_arrShip(iLoop2).yPos)), 5, " ") + "  " + _
                "dx: " + LeftPadString$(SngRoundedToStr$(m_arrShip(iLoop2).dx, 5), 5, " ") + "  " + _
                "dy: " + LeftPadString$(SngRoundedToStr$(m_arrShip(iLoop2).dy, 5), 5, " ") + "  " + _
                IIFSTR$ ( m_arrShip(iLoop2).Left_IsPressed, "LEFT ", "") + _
                IIFSTR$ ( m_arrShip(iLoop2).Right_IsPressed, "RIGHT ", "") + _
                IIFSTR$ ( m_arrShip(iLoop2).Up_IsPressed, "UP ", "") + _
                IIFSTR$ ( m_arrShip(iLoop2).Down_IsPressed, "DOWN ", "") + _
                IIFSTR$ ( m_arrShip(iLoop2).Shoot_IsPressed, "FIRE ", "") + _
                IIFSTR$ ( m_arrShip(iLoop2).Cheat_IsPressed, "TILT ", "") + _
                ""
        iRow = iRow + 2
        'end if
    Next iLoop2
   
   
   
   
   
    'Color cPurple
    ''PrintAt 3, 0, "Facing: " + SngRoundedToStr$(m_arrShip(iLoop1).facing, 5)
    'PrintAt 3, 0, "     dx: " + SngRoundedToStr$(m_arrShip(iLoop1).dx, 5)
    'PrintAt 4, 0, "     vx: " + SngRoundedToStr$(m_arrShip(iLoop1).vx, 5)
    'PrintAt 5, 0, "m_arrDX: " + SngRoundedToStr$(m_arrDX(m_arrShip(iLoop1).dx), 5)
    '
    'PrintAt 7, 0, "    dy: " + SngRoundedToStr$(m_arrShip(iLoop1).dy, 5)
    'PrintAt 8, 0, "    vy: " + SngRoundedToStr$(m_arrShip(iLoop1).vy, 5)
    'PrintAt 9, 0, "m_arrDY: " + SngRoundedToStr$(m_arrDY(m_arrShip(iLoop1).dy), 5)
   
    ' Show instructions
    'TODO: SHOW INSTRUCTIONS (MAKE KEYCODE TO TEXT FUNCTIONS?)
    'Color cRed, cRed
    'PrintAt 45, 0, String$(120, " ")
    'Color cWhite, cEmpty
    'PrintAt 45, 0, "CONTROLS: LEFT/RIGHT = TURN   UP/DOWN = FORWARD/BACK   CTRL=FIRE   1=ADD SHIELD (CHEAT)"
   
End Sub ' ShowScore

' /////////////////////////////////////////////////////////////////////////////
' Draw the ship

Sub DrawAllShips ()
    Dim iLoop2 As Integer
    For iLoop2 = 1 To m_iPlayers
        If m_arrShip(iLoop2).shields > 0 Then
            DrawShip iLoop2
        End If
    Next iLoop2
End Sub ' DrawAllShips

' /////////////////////////////////////////////////////////////////////////////
' Draw the ship

Sub DrawShip (iShipIndex As Integer)
    Dim intX1 As Integer
    Dim intY1 As Integer
    Dim intX2 As Integer
    Dim intY2 As Integer
    Dim intX3 As Integer
    Dim intY3 As Integer
   
    Dim intX1b As Integer ' engine flame ends here
    Dim intY1b As Integer ' engine flame ends here
    Dim intX2b As Integer ' back wall starts here on left
    Dim intY2b As Integer ' back wall starts here on left
    Dim intX3b As Integer ' back wall starts here on right
    Dim intY3b As Integer ' back wall starts here on right
   
    ' -----------------------------------------------------------------------------
    ' Set the coordinates of the ship
   
    ' front
    intX1 = m_arrShip(iShipIndex).xPos + SHIP_RADIUS * Sin(m_arrShip(iShipIndex).facing)
    intY1 = m_arrShip(iShipIndex).yPos - SHIP_RADIUS * Cos(m_arrShip(iShipIndex).facing)
   
    ' left rear
    intX2 = m_arrShip(iShipIndex).xPos + SHIP_RADIUS * Sin(m_arrShip(iShipIndex).facing + 2 * PI / 3)
    intY2 = m_arrShip(iShipIndex).yPos - SHIP_RADIUS * Cos(m_arrShip(iShipIndex).facing + 2 * PI / 3)
   
    ' left rear (3/4 of the way down)
    intX2b = m_arrShip(iShipIndex).xPos + ENGINE_RADIUS * Sin(m_arrShip(iShipIndex).facing + 2 * PI / 3)
    intY2b = m_arrShip(iShipIndex).yPos - ENGINE_RADIUS * Cos(m_arrShip(iShipIndex).facing + 2 * PI / 3)
   
    ' right rear
    intX3 = m_arrShip(iShipIndex).xPos + SHIP_RADIUS * Sin(m_arrShip(iShipIndex).facing + 4 * PI / 3)
    intY3 = m_arrShip(iShipIndex).yPos - SHIP_RADIUS * Cos(m_arrShip(iShipIndex).facing + 4 * PI / 3)

    ' right rear (3/4 of the way down)
    intX3b = m_arrShip(iShipIndex).xPos + ENGINE_RADIUS * Sin(m_arrShip(iShipIndex).facing + 4 * PI / 3)
    intY3b = m_arrShip(iShipIndex).yPos - ENGINE_RADIUS * Cos(m_arrShip(iShipIndex).facing + 4 * PI / 3)
   
    ' rear where engine flames end
    intX1b = m_arrShip(iShipIndex).xPos - FLAME_RADIUS * Sin(m_arrShip(iShipIndex).facing)
    intY1b = m_arrShip(iShipIndex).yPos + FLAME_RADIUS * Cos(m_arrShip(iShipIndex).facing)
    'm_arrShip(iShipIndex).facing = m_arrShip(iShipIndex).facing - Pi / 36

    ' -----------------------------------------------------------------------------
    ' Draw the ship
   
    ' Draw the left side
    Line (intX1, intY1)-(intX2, intY2), m_arrShip(iShipIndex).BodyColor
   
    ' Draw the right side
    Line (intX1, intY1)-(intX3, intY3), m_arrShip(iShipIndex).BodyColor
   
    ' Draw the rear / aft side
    If m_arrShip(iShipIndex).Up_IsPressed Then
        ' Draw rear side
        Line (intX2b, intY2b)-(intX3b, intY3b), m_arrShip(iShipIndex).EngineColor
       
        ' Engine is firing
        'Line (intX2b, intY2b)-(intX3b, intY3b), m_arrShip(iShipIndex).FlameColor
       
        ' Draw the flame left side
        Line (intX1b, intY1b)-(intX2b, intY2b), m_arrShip(iShipIndex).FlameColor
       
        ' Draw the flame right side
        Line (intX1b, intY1b)-(intX3b, intY3b), m_arrShip(iShipIndex).FlameColor
       
    Else
        ' Draw rear side
        Line (intX2b, intY2b)-(intX3b, intY3b), m_arrShip(iShipIndex).EngineColor
    End If
   
End Sub ' DrawShip

' /////////////////////////////////////////////////////////////////////////////
' Respawn the enemies if there all dead

Sub Respawn ()
    Dim iLoop1 As Integer ' Used for variables
    Dim iLoop2 As Integer ' Used for variables
    Dim bDone As Integer
   
    ' Check if all enemies are dead
    m_bAllDead = TRUE
    For iLoop1 = 0 To UBound(m_arrEnemy)
        ' If an enemy is alive then
        If m_arrEnemy(iLoop1).alive = TRUE Then
            ' enemies aren't all dead
            m_bAllDead = FALSE
            Exit For
        End If
    Next iLoop1
   
    ' if all dead, respawn and create one more enemy
    ' advance to next level
    If m_bAllDead = TRUE Then
        ' INCREASE LEVEL
        m_iLevel = m_iLevel + 1
       
        ' GIVE SURVIVING PLAYERS SOME BONUS SHIELDS
        ' AND A LEVEL BONUS
        ' AND RECORD THE HIGHEST LEVEL EACH HAS REACHED
        ' TODO: ADD OPTION FOR DEAD PLAYERS TO BE RESURRECTED IN NEXT ROUND
        ' TODO: MAYBE ADD SOME INTERESTING WAY TO DETERMINE LEVEL BONUS
        For iLoop2 = 1 To m_iPlayers
            If m_arrShip(iLoop2).shields > 0 Then
                m_arrShip(iLoop2).shields = m_arrShip(iLoop2).shields + BONUS_SHIELDS
                m_arrShip(iLoop2).level = m_arrShip(iLoop2).level + 1
                m_arrShip(iLoop2).score = m_arrShip(iLoop2).score + 100
            End If
        Next iLoop2
       
        ' INCREASE THE NUMBER OF ENEMIES
        ReDim _Preserve m_arrEnemy(UBound(m_arrEnemy) + 1) As EnemyType
       
        ' INCREASE VARIETY OF ENEMY SIZES
        If m_iMinEnemyRadius > MIN_ENEMY_RADIUS Then
            m_iMinEnemyRadius = m_iMinEnemyRadius - 1
        End If
        If m_iMaxEnemyRadius < MAX_ENEMY_RADIUS Then
            m_iMaxEnemyRadius = m_iMaxEnemyRadius + 1
        End If

        ' SPAWN NEW WAVE OF ENEMIES
        For iLoop1 = 0 To UBound(m_arrEnemy)
            ' Set the starting positions
            m_arrEnemy(iLoop1).alive = TRUE
            m_arrEnemy(iLoop1).life = 30
            'm_arrEnemy(iLoop1).xPos = m_arrShip(iLoop1).xPos
            'm_arrEnemy(iLoop1).yPos = m_arrShip(iLoop1).yPos
           
            ' choose a random size
            m_arrEnemy(iLoop1).radius = RandomNumber%(m_iMinEnemyRadius, m_iMaxEnemyRadius)
           
            ' make sure the enemies don't start on the ship
            bDone = FALSE
            Do
                m_arrEnemy(iLoop1).xPos = RandomNumber%(iMinX, iMaxX) 'Int(Rnd * ScaleWidth + 1)
                m_arrEnemy(iLoop1).yPos = RandomNumber%(iMinY, iMaxY) 'Int(Rnd * ScaleHeight + 1)
               
                For iLoop2 = 1 To m_iPlayers
                    If m_arrShip(iLoop2).shields > 0 Then
                        If GetDist(m_arrEnemy(iLoop1).xPos, m_arrEnemy(iLoop1).yPos, m_arrShip(iLoop2).xPos, m_arrShip(iLoop2).yPos) > SHIP_RADIUS * 10 Then
                            bDone = TRUE
                            Exit For
                        End If
                    End If
                Next iLoop2
               
                If bDone Then Exit Do
            Loop
           
        Next iLoop1
    End If
End Sub ' Respawn

' /////////////////////////////////////////////////////////////////////////////

Function GetDist! (sngX1 As Single, sngY1 As Single, sngX2 As Single, sngY2 As Single) ' As Single
    Dim sngXComp As Single
    Dim sngYComp As Single
   
    ' Set the X componate
    sngXComp = sngX2 - sngX1
   
    ' Set the Y Componate
    sngYComp = sngY1 - sngY2
   
    ' Get the distance between the two objects
    GetDist = Sqr(sngXComp ^ 2 + sngYComp ^ 2)
End Function ' GetDist

' /////////////////////////////////////////////////////////////////////////////

Function GetAngle (sngX1 As Single, sngY1 As Single, sngX2 As Single, sngY2 As Single)
    Dim sngXComp As Single
    Dim sngYComp As Single
   
    ' Set the X componate
    sngXComp = sngX2 - sngX1
   
    ' Set the Y componate
    sngYComp = sngY1 - sngY2
   
    ' Calculate the resultant angle, and adjust for actangent by adding Pi if necessary
    If Sgn(sngYComp) > 0 Then
        GetAngle = Atn(sngXComp / sngYComp)
    End If
    If Sgn(sngYComp) < 0 Then
        GetAngle = Atn(sngXComp / sngYComp) + PI
    End If
End Function ' GetAngle

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' END GAME CODE @GAME
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' BEGIN DX/DY TABLES #DYXDY
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' /////////////////////////////////////////////////////////////////////////////
' Initializes tables that convert rotation angle to DX, DY values.
' Where the index 0-143 corresponds to 0-359 degrees
'
' Requires the following shared variables be declared:
'     Dim Shared m_arrDX(0 To 143) As Single
'     Dim Shared m_arrDY(0 To 143) As Single

Sub InitDxDyTables
    m_arrDX(0) = 0
    m_arrDX(1) = 0.0277777777777778
    m_arrDX(2) = 0.0555555555555556
    m_arrDX(3) = 0.0833333333333333
    m_arrDX(4) = 0.111111111111111
    m_arrDX(5) = 0.138888888888889
    m_arrDX(6) = 0.166666666666667
    m_arrDX(7) = 0.194444444444444
    m_arrDX(8) = 0.222222222222222
    m_arrDX(9) = 0.25
    m_arrDX(10) = 0.277777777777778
    m_arrDX(11) = 0.305555555555556
    m_arrDX(12) = 0.333333333333333
    m_arrDX(13) = 0.361111111111111
    m_arrDX(14) = 0.388888888888889
    m_arrDX(15) = 0.416666666666667
    m_arrDX(16) = 0.444444444444445
    m_arrDX(17) = 0.472222222222222
    m_arrDX(18) = 0.5
    m_arrDX(19) = 0.527777777777778
    m_arrDX(20) = 0.555555555555556
    m_arrDX(21) = 0.583333333333333
    m_arrDX(22) = 0.611111111111111
    m_arrDX(23) = 0.638888888888889
    m_arrDX(24) = 0.666666666666667
    m_arrDX(25) = 0.694444444444445
    m_arrDX(26) = 0.722222222222222
    m_arrDX(27) = 0.75
    m_arrDX(28) = 0.777777777777778
    m_arrDX(29) = 0.805555555555556
    m_arrDX(30) = 0.833333333333334
    m_arrDX(31) = 0.861111111111111
    m_arrDX(32) = 0.888888888888889
    m_arrDX(33) = 0.916666666666667
    m_arrDX(34) = 0.944444444444445
    m_arrDX(35) = 0.972222222222223
    m_arrDX(36) = 1
    m_arrDX(37) = 0.972222222222222
    m_arrDX(38) = 0.944444444444445
    m_arrDX(39) = 0.916666666666667
    m_arrDX(40) = 0.888888888888889
    m_arrDX(41) = 0.861111111111111
    m_arrDX(42) = 0.833333333333333
    m_arrDX(43) = 0.805555555555556
    m_arrDX(44) = 0.777777777777778
    m_arrDX(45) = 0.75
    m_arrDX(46) = 0.722222222222222
    m_arrDX(47) = 0.694444444444445
    m_arrDX(48) = 0.666666666666667
    m_arrDX(49) = 0.638888888888889
    m_arrDX(50) = 0.611111111111111
    m_arrDX(51) = 0.583333333333333
    m_arrDX(52) = 0.555555555555556
    m_arrDX(53) = 0.527777777777778
    m_arrDX(54) = 0.5
    m_arrDX(55) = 0.472222222222222
    m_arrDX(56) = 0.444444444444444
    m_arrDX(57) = 0.416666666666667
    m_arrDX(58) = 0.388888888888889
    m_arrDX(59) = 0.361111111111111
    m_arrDX(60) = 0.333333333333333
    m_arrDX(61) = 0.305555555555555
    m_arrDX(62) = 0.277777777777778
    m_arrDX(63) = 0.25
    m_arrDX(64) = 0.222222222222222
    m_arrDX(65) = 0.194444444444444
    m_arrDX(66) = 0.166666666666667
    m_arrDX(67) = 0.138888888888889
    m_arrDX(68) = 0.111111111111111
    m_arrDX(69) = 0.0833333333333332
    m_arrDX(70) = 0.0555555555555554
    m_arrDX(71) = 0.0277777777777776
    m_arrDX(72) = 0 ' -1.52655665885959E-16
    m_arrDX(73) = -0.0277777777777779
    m_arrDX(74) = -0.0555555555555557
    m_arrDX(75) = -0.0833333333333335
    m_arrDX(76) = -0.111111111111111
    m_arrDX(77) = -0.138888888888889
    m_arrDX(78) = -0.166666666666667
    m_arrDX(79) = -0.194444444444445
    m_arrDX(80) = -0.222222222222222
    m_arrDX(81) = -0.25
    m_arrDX(82) = -0.277777777777778
    m_arrDX(83) = -0.305555555555556
    m_arrDX(84) = -0.333333333333334
    m_arrDX(85) = -0.361111111111111
    m_arrDX(86) = -0.388888888888889
    m_arrDX(87) = -0.416666666666667
    m_arrDX(88) = -0.444444444444445
    m_arrDX(89) = -0.472222222222222
    m_arrDX(90) = -0.5
    m_arrDX(91) = -0.527777777777778
    m_arrDX(92) = -0.555555555555556
    m_arrDX(93) = -0.583333333333334
    m_arrDX(94) = -0.611111111111111
    m_arrDX(95) = -0.638888888888889
    m_arrDX(96) = -0.666666666666667
    m_arrDX(97) = -0.694444444444445
    m_arrDX(98) = -0.722222222222223
    m_arrDX(99) = -0.75
    m_arrDX(100) = -0.777777777777778
    m_arrDX(101) = -0.805555555555556
    m_arrDX(102) = -0.833333333333334
    m_arrDX(103) = -0.861111111111111
    m_arrDX(104) = -0.888888888888889
    m_arrDX(105) = -0.916666666666667
    m_arrDX(106) = -0.944444444444445
    m_arrDX(107) = -0.972222222222223
    m_arrDX(108) = -1
    m_arrDX(109) = -0.972222222222223
    m_arrDX(110) = -0.944444444444445
    m_arrDX(111) = -0.916666666666667
    m_arrDX(112) = -0.888888888888889
    m_arrDX(113) = -0.861111111111111
    m_arrDX(114) = -0.833333333333334
    m_arrDX(115) = -0.805555555555556
    m_arrDX(116) = -0.777777777777778
    m_arrDX(117) = -0.75
    m_arrDX(118) = -0.722222222222223
    m_arrDX(119) = -0.694444444444445
    m_arrDX(120) = -0.666666666666667
    m_arrDX(121) = -0.638888888888889
    m_arrDX(122) = -0.611111111111111
    m_arrDX(123) = -0.583333333333334
    m_arrDX(124) = -0.555555555555556
    m_arrDX(125) = -0.527777777777778
    m_arrDX(126) = -0.5
    m_arrDX(127) = -0.472222222222222
    m_arrDX(128) = -0.444444444444445
    m_arrDX(129) = -0.416666666666667
    m_arrDX(130) = -0.388888888888889
    m_arrDX(131) = -0.361111111111111
    m_arrDX(132) = -0.333333333333333
    m_arrDX(133) = -0.305555555555556
    m_arrDX(134) = -0.277777777777778
    m_arrDX(135) = -0.25
    m_arrDX(136) = -0.222222222222222
    m_arrDX(137) = -0.194444444444445
    m_arrDX(138) = -0.166666666666667
    m_arrDX(139) = -0.138888888888889
    m_arrDX(140) = -0.111111111111111
    m_arrDX(141) = -0.0833333333333334
    m_arrDX(142) = -0.0555555555555556
    m_arrDX(143) = -0.0277777777777778
   
    m_arrDY(0) = -1
    m_arrDY(1) = -0.972222222222222
    m_arrDY(2) = -0.944444444444444
    m_arrDY(3) = -0.916666666666667
    m_arrDY(4) = -0.888888888888889
    m_arrDY(5) = -0.861111111111111
    m_arrDY(6) = -0.833333333333333
    m_arrDY(7) = -0.805555555555555
    m_arrDY(8) = -0.777777777777778
    m_arrDY(9) = -0.75
    m_arrDY(10) = -0.722222222222222
    m_arrDY(11) = -0.694444444444444
    m_arrDY(12) = -0.666666666666667
    m_arrDY(13) = -0.638888888888889
    m_arrDY(14) = -0.611111111111111
    m_arrDY(15) = -0.583333333333333
    m_arrDY(16) = -0.555555555555555
    m_arrDY(17) = -0.527777777777778
    m_arrDY(18) = -0.5
    m_arrDY(19) = -0.472222222222222
    m_arrDY(20) = -0.444444444444444
    m_arrDY(21) = -0.416666666666666
    m_arrDY(22) = -0.388888888888889
    m_arrDY(23) = -0.361111111111111
    m_arrDY(24) = -0.333333333333333
    m_arrDY(25) = -0.305555555555555
    m_arrDY(26) = -0.277777777777777
    m_arrDY(27) = -0.25
    m_arrDY(28) = -0.222222222222222
    m_arrDY(29) = -0.194444444444444
    m_arrDY(30) = -0.166666666666666
    m_arrDY(31) = -0.138888888888889
    m_arrDY(32) = -0.111111111111111
    m_arrDY(33) = -0.083333333333333
    m_arrDY(34) = -0.0555555555555552
    m_arrDY(35) = -0.0277777777777774
    m_arrDY(36) = 0 ' 3.7470027081099E-16
    m_arrDY(37) = 0.0277777777777782
    m_arrDY(38) = 0.0555555555555559
    m_arrDY(39) = 0.0833333333333337
    m_arrDY(40) = 0.111111111111111
    m_arrDY(41) = 0.138888888888889
    m_arrDY(42) = 0.166666666666667
    m_arrDY(43) = 0.194444444444445
    m_arrDY(44) = 0.222222222222223
    m_arrDY(45) = 0.25
    m_arrDY(46) = 0.277777777777778
    m_arrDY(47) = 0.305555555555556
    m_arrDY(48) = 0.333333333333334
    m_arrDY(49) = 0.361111111111112
    m_arrDY(50) = 0.388888888888889
    m_arrDY(51) = 0.416666666666667
    m_arrDY(52) = 0.444444444444445
    m_arrDY(53) = 0.472222222222223
    m_arrDY(54) = 0.5
    m_arrDY(55) = 0.527777777777778
    m_arrDY(56) = 0.555555555555556
    m_arrDY(57) = 0.583333333333334
    m_arrDY(58) = 0.611111111111112
    m_arrDY(59) = 0.638888888888889
    m_arrDY(60) = 0.666666666666667
    m_arrDY(61) = 0.694444444444445
    m_arrDY(62) = 0.722222222222223
    m_arrDY(63) = 0.750000000000001
    m_arrDY(64) = 0.777777777777778
    m_arrDY(65) = 0.805555555555556
    m_arrDY(66) = 0.833333333333334
    m_arrDY(67) = 0.861111111111112
    m_arrDY(68) = 0.88888888888889
    m_arrDY(69) = 0.916666666666667
    m_arrDY(70) = 0.944444444444445
    m_arrDY(71) = 0.972222222222223
    m_arrDY(72) = 1
    m_arrDY(73) = 0.972222222222223
    m_arrDY(74) = 0.944444444444445
    m_arrDY(75) = 0.916666666666667
    m_arrDY(76) = 0.88888888888889
    m_arrDY(77) = 0.861111111111112
    m_arrDY(78) = 0.833333333333334
    m_arrDY(79) = 0.805555555555556
    m_arrDY(80) = 0.777777777777778
    m_arrDY(81) = 0.750000000000001
    m_arrDY(82) = 0.722222222222223
    m_arrDY(83) = 0.694444444444445
    m_arrDY(84) = 0.666666666666667
    m_arrDY(85) = 0.638888888888889
    m_arrDY(86) = 0.611111111111112
    m_arrDY(87) = 0.583333333333334
    m_arrDY(88) = 0.555555555555556
    m_arrDY(89) = 0.527777777777778
    m_arrDY(90) = 0.5
    m_arrDY(91) = 0.472222222222223
    m_arrDY(92) = 0.444444444444445
    m_arrDY(93) = 0.416666666666667
    m_arrDY(94) = 0.388888888888889
    m_arrDY(95) = 0.361111111111111
    m_arrDY(96) = 0.333333333333334
    m_arrDY(97) = 0.305555555555556
    m_arrDY(98) = 0.277777777777778
    m_arrDY(99) = 0.25
    m_arrDY(100) = 0.222222222222223
    m_arrDY(101) = 0.194444444444445
    m_arrDY(102) = 0.166666666666667
    m_arrDY(103) = 0.138888888888889
    m_arrDY(104) = 0.111111111111111
    m_arrDY(105) = 0.0833333333333336
    m_arrDY(106) = 0.0555555555555558
    m_arrDY(107) = 0.0277777777777781
    m_arrDY(108) = 0 ' 2.91433543964104E-16
    m_arrDY(109) = -0.0277777777777775
    m_arrDY(110) = -0.0555555555555553
    m_arrDY(111) = -0.083333333333333
    m_arrDY(112) = -0.111111111111111
    m_arrDY(113) = -0.138888888888889
    m_arrDY(114) = -0.166666666666666
    m_arrDY(115) = -0.194444444444444
    m_arrDY(116) = -0.222222222222222
    m_arrDY(117) = -0.25
    m_arrDY(118) = -0.277777777777778
    m_arrDY(119) = -0.305555555555555
    m_arrDY(120) = -0.333333333333333
    m_arrDY(121) = -0.361111111111111
    m_arrDY(122) = -0.388888888888889
    m_arrDY(123) = -0.416666666666666
    m_arrDY(124) = -0.444444444444444
    m_arrDY(125) = -0.472222222222222
    m_arrDY(126) = -0.5
    m_arrDY(127) = -0.527777777777778
    m_arrDY(128) = -0.555555555555555
    m_arrDY(129) = -0.583333333333333
    m_arrDY(130) = -0.611111111111111
    m_arrDY(131) = -0.638888888888889
    m_arrDY(132) = -0.666666666666667
    m_arrDY(133) = -0.694444444444444
    m_arrDY(134) = -0.722222222222222
    m_arrDY(135) = -0.75
    m_arrDY(136) = -0.777777777777778
    m_arrDY(137) = -0.805555555555555
    m_arrDY(138) = -0.833333333333333
    m_arrDY(139) = -0.861111111111111
    m_arrDY(140) = -0.888888888888889
    m_arrDY(141) = -0.916666666666667
    m_arrDY(142) = -0.944444444444444
    m_arrDY(143) = -0.972222222222222
End Sub ' InitDxDyTables

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' END DX/DY TABLES @DXDY
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' BEGIN TRIG FUNCTIONS #TRIG
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' /////////////////////////////////////////////////////////////////////////////
' use angles in degrees units instead of radians (converted inside sub)
' Note this function uses whatever the default type is, better not be some Integer Type.

Function CosD (degrees)
    CosD = Cos(_D2R(degrees))
End Function ' CosD

' /////////////////////////////////////////////////////////////////////////////
' use angles in degrees units instead of radians (converted inside sub)
' Note this function uses whatever the default type is, better not be some Integer Type.

Function DAtan2 (x1, y1, x2, y2) ' The angle in degrees a 2nd point (x2, y2)  makes to a first point (x1, y1)
    ' Delta means change between 1 measure and another for example x2 - x1
    deltaX = x2 - x1
    deltaY = y2 - y1
   
    ' To find the angle point(x2, y2) makes to (x1, y1) in Degrees
    ' Take DegreeAngle = DAtan2(y2 - y1, x2 - x1)
    rtn = _R2D(_Atan2(deltaY, deltaX))
    If rtn < 0 Then
        DAtan2 = rtn + 360
    Else
        DAtan2 = rtn
    End If
End Function ' DAtan2

' /////////////////////////////////////////////////////////////////////////////

Sub ShowDegreesAndRadians
    Dim iDegree As Integer
    Dim sngRadian As Single
   
    DebugPrint "Degree   Radian"
    DebugPrint "------   ------"
    For iDegree = 0 To 360
        sngRadian = _D2R(iDegree)
       
        'DebugPrint LeftPadString$(cstr$(iDegree), 3, " ") + "   " + LeftPadString$(cstr$(iRadian), 3, " ")
       
        DebugPrint LeftPadString$(cstr$(iDegree), 3, " ") + "   " + SngToStr$(sngRadian)
       
        'Print "SngToStr$(MyValue)           =" + SngToStr$(MyValue)
        'Print "SngRoundedToStr$(MyValue, 12)=" + SngRoundedToStr$(MyValue, 12)
       
    Next iDegree
End Sub ' ShowDegreesAndRadians

' /////////////////////////////////////////////////////////////////////////////
' use angles in degrees units instead of radians (converted inside sub)
' Note this function uses whatever the default type is, better not be some Integer Type.

Function SinD (degrees)
    SinD = Sin(_D2R(degrees))
End Function ' SinD

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' END TRIG FUNCTIONS @TRIG
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' BEGIN GENERAL PURPOSE ROUTINES #GEN
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' /////////////////////////////////////////////////////////////////////////////

Function AppendString$ (MyString As String, NewString As String, MyDelimiter As String)
    Dim sResult As String: sResult = MyString
    If Len(MyString) > 0 Then
        sResult = sResult + MyDelimiter
    End If
    sResult = sResult + NewString
    AppendString$ = sResult
End Function ' AppendString$

' /////////////////////////////////////////////////////////////////////////////

Sub AppendToStringArray (MyStringArray$(), MyString$)
    ReDim _Preserve MyStringArray$(LBound(MyStringArray$) To UBound(MyStringArray$) + 1)
    MyStringArray$(UBound(MyStringArray$)) = MyString$
End Sub ' AppendToStringArray

' /////////////////////////////////////////////////////////////////////////////
' See also StringTo2dArray

Function Array2dToString$ (MyArray() As String)
    Dim MyString As String
    Dim iY As Integer
    Dim iX As Integer
    Dim sLine As String
    MyString = ""
    For iY = LBound(MyArray, 1) To UBound(MyArray, 1)
        sLine = ""
        For iX = LBound(MyArray, 2) To UBound(MyArray, 2)
            sLine = sLine + MyArray(iY, iX)
        Next iX
        MyString = MyString + sLine + Chr$(13)
    Next iY
    Array2dToString$ = MyString
End Function ' Array2dToString$

' /////////////////////////////////////////////////////////////////////////////

'Function Array2dToStringTest$ (MyArray() As String)
'    Dim MyString As String
'    Dim iY As Integer
'    Dim iX As Integer
'    Dim sLine As String
'    MyString = ""
'    MyString = MyString + "           11111111112222222222333" + Chr$(13)
'    MyString = MyString + "  12345678901234567890123456789012" + Chr$(13)
'    For iY = LBound(MyArray, 1) To UBound(MyArray, 1)
'        sLine = ""
'        sLine = sLine + Right$("  " + cstr$(iY), 2)
'        For iX = LBound(MyArray, 2) To UBound(MyArray, 2)
'            sLine = sLine + MyArray(iY, iX)
'        Next iX
'        sLine = sLine + Right$("  " + cstr$(iY), 2)
'        MyString = MyString + sLine + Chr$(13)
'    Next iY
'    MyString = MyString + "  12345678901234567890123456789012" + Chr$(13)
'    MyString = MyString + "           11111111112222222222333" + Chr$(13)
'    Array2dToStringTest$ = MyString
'End Function ' Array2dToStringTest$

' /////////////////////////////////////////////////////////////////////////////
' Integer to string

Function cstr$ (myValue)
    'cstr$ = LTRIM$(RTRIM$(STR$(myValue)))
    cstr$ = _Trim$(Str$(myValue))
End Function ' cstr$

'' /////////////////////////////////////////////////////////////////////////////
'' Long to string
'
'Function cstrl$ (myValue As Long)
'    cstrl$ = _Trim$(Str$(myValue))
'End Function ' cstrl$
'
'' /////////////////////////////////////////////////////////////////////////////
'' Single to string
'
'Function cstrs$ (myValue As Single)
'    ''cstr$ = LTRIM$(RTRIM$(STR$(myValue)))
'    cstrs$ = _Trim$(Str$(myValue))
'End Function ' cstrs$
'
'' /////////////////////////////////////////////////////////////////////////////
'' Unsigned Long to string
'
'Function cstrul$ (myValue As _Unsigned Long)
'    cstrul$ = _Trim$(Str$(myValue))
'End Function ' cstrul$

' /////////////////////////////////////////////////////////////////////////////
' Simple timestamp function

Function CurrentDateTime$
    CurrentDateTime$ = Mid$(Date$, 7, 4) + "-" + _
        Mid$(Date$, 1, 5) + " " + _
        Time$
End Function ' CurrentDateTime$

' /////////////////////////////////////////////////////////////////////////////
' Hack function, to be replaced with something better

Function DblToInt% (dblOld As Double)
    Dim dblNew As Double
    Dim sValue As String
    Dim iPos As Integer

    dblNew = RoundDouble#(dblOld, 0)
    'sValue = _Trim$(Str$(dblNew))

    sValue = DblToStr$(dblNew)

    'iPos = InStr(1, sValue, ".")
    'If iPos > 0 Then
    '    DblToInt% = Val(Left$(sValue, iPos - 1))
    'Else
    '    DblToInt% = Val(sValue)
    'End If

    DblToInt% = Val(sValue)
End Function ' DblToInt%

' /////////////////////////////////////////////////////////////////////////////
' Scientific notation - QB64 Wiki
' https://www.qb64.org/wiki/Scientific_notation

' Example: A string function that displays extremely small or large exponential decimal values.

Function DblToStr$ (n#)
    Dim result$: result$ = ""
    Dim value$
    Dim Xpos%
    Dim expo%
    Dim sign$
    Dim valu$
    Dim dot%
    Dim L%
    Dim add$
    Dim min$
    Dim DP$
    Dim n%

    value$ = UCase$(LTrim$(Str$(n#)))
    Xpos% = InStr(value$, "D") + InStr(value$, "E") ' only D or E can be present
    If Xpos% Then
        expo% = Val(Mid$(value$, Xpos% + 1))
        If Val(value$) < 0 Then
            sign$ = "-"
            valu$ = Mid$(value$, 2, Xpos% - 2)
        Else
            valu$ = Mid$(value$, 1, Xpos% - 1)
        End If
        dot% = InStr(valu$, ".")
        L% = Len(valu$)
        If expo% > 0 Then
            add$ = String$(expo% - (L% - dot%), "0")
        End If
        If expo% < 0 Then
            min$ = String$(Abs(expo%) - (dot% - 1), "0")
            DP$ = "."
        End If
        For n% = 1 To L%
            If Mid$(valu$, n%, 1) <> "." Then
                num$ = num$ + Mid$(valu$, n%, 1)
            End If
        Next n%
        result$ = _Trim$(sign$ + DP$ + min$ + num$ + add$)
    Else
        result$ = value$
    End If

    DblToStr$ = result$
End Function ' DblToStr$

' /////////////////////////////////////////////////////////////////////////////

Function DblRoundedToStr$ (dblValue As Double, intNumPlaces As Integer)
    Dim dblNew As Double
    dblNew = RoundDouble#(dblValue, intNumPlaces)
    DblRoundedToStr$ = DblToStr$(dblNew)
End Function ' DblRoundedToStr$

' /////////////////////////////////////////////////////////////////////////////
' Receives an {sDelim} delimited list {sInput}
' returns the list with all duplicate entries removed.

Function DedupeDelimList$ (sInput As String, sDelim As String)
    ReDim arrLines(-1) As String
    Dim sOutput As String
    Dim iLoop As Integer

    split sInput, sDelim, arrLines()
    sOutput = sDelim
    For iLoop = LBound(arrLines) To UBound(arrLines)
        If InStr(1, sOutput, sDelim + arrLines(iLoop) + sDelim) = 0 Then
            sOutput = sOutput + arrLines(iLoop) + sDelim
        End If
    Next iLoop

    DedupeDelimList$ = sOutput
End Function ' DedupeDelimList$

' /////////////////////////////////////////////////////////////////////////////

Function DoubleABS# (dblValue As Double)
    If Sgn(dblValue) = -1 Then
        DoubleABS# = 0 - dblValue
    Else
        DoubleABS# = dblValue
    End If
End Function ' DoubleABS#

' /////////////////////////////////////////////////////////////////////////////
' bplus: The Gold Standard is even better than THE QB64 CIRCLE sub in this respect!
' https://forum.qb64.org/index.php?topic=1044.135

' from Steve Gold standard
' Renamed fcirc to DrawCircleSolid

' Not as fast as DrawCircleTopLeft but pretty fast.

' Example:
' Screen _NewImage(800, 600, 32)
' _ScreenMove 250, 60
' For r = 250 To 0 Step -60
'     DrawCircleSolid 400, 300, r, _RGBA(255, 255, 255, 100)
' Next r

Sub DrawCircleSolid (CX As Integer, CY As Integer, R As Integer, C As _Unsigned Long)
    Dim Radius As Integer, RadiusError As Integer
    Dim X As Integer, Y As Integer

    Radius = Abs(R)
    RadiusError = -Radius
    X = Radius
    Y = 0

    If Radius = 0 Then PSet (CX, CY), C: Exit Sub

    ' Draw the middle span here so we don't draw it twice in the main loop,
    ' which would be a problem with blending turned on.
    Line (CX - X, CY)-(CX + X, CY), C, BF

    While X > Y
        RadiusError = RadiusError + Y * 2 + 1
        If RadiusError >= 0 Then
            If X <> Y + 1 Then
                Line (CX - Y, CY - X)-(CX + Y, CY - X), C, BF
                Line (CX - Y, CY + X)-(CX + Y, CY + X), C, BF
            End If
            X = X - 1
            RadiusError = RadiusError - X * 2
        End If
        Y = Y + 1
        Line (CX - X, CY - Y)-(CX + X, CY - Y), C, BF
        Line (CX - X, CY + Y)-(CX + X, CY + Y), C, BF
    Wend
End Sub ' DrawCircleSolid

' /////////////////////////////////////////////////////////////////////////////
' SMcNeill
' More efficient version of ExtendedTimer.
' This stores our day values and only updates them when necessary.
' We really don't need to figure out how many seconds are in today over and over endlessly
' -- just count them once, and when the clock swaps back to 0:0:0, add 24*60*60 seconds to the count.
' Re: how to time something (ie do loop for n seconds)
' https://forum.qb64.org/index.php?topic=4682.0

Function ExtendedTimer##
    'modified extendedtimer to store the old day's count, and not have to recalculate it every time the routine is called.

    Static olds As _Float, old_day As _Float
    Dim m As Integer, d As Integer, y As Integer
    Dim s As _Float, day As String
    If olds = 0 Then 'calculate the day the first time the extended timer runs
        day = Date$
        m = Val(Left$(day, 2))
        d = Val(Mid$(day, 4, 2))
        y = Val(Right$(day, 4)) - 1970
        Select Case m 'Add the number of days for each previous month passed
            Case 2: d = d + 31
            Case 3: d = d + 59
            Case 4: d = d + 90
            Case 5: d = d + 120
            Case 6: d = d + 151
            Case 7: d = d + 181
            Case 8: d = d + 212
            Case 9: d = d + 243
            Case 10: d = d + 273
            Case 11: d = d + 304
            Case 12: d = d + 334
        End Select
        If (y Mod 4) = 2 And m > 2 Then d = d + 1 'add a day if this is leap year and we're past february
        d = (d - 1) + 365 * y 'current month days passed + 365 days per each standard year
        d = d + (y + 2) \ 4 'add in days for leap years passed
        s = d * 24 * 60 * 60 'Seconds are days * 24 hours * 60 minutes * 60 seconds
        old_day = s
    End If
    If Timer < oldt Then 'we went from 23:59:59 (a second before midnight) to 0:0:0 (midnight)
        old_day = s + 83400 'add another worth of seconds to our counter
    End If
    oldt = Timer
    olds = old_day + oldt
    ExtendedTimer## = olds
End Function ' ExtendedTimer##

' /////////////////////////////////////////////////////////////////////////////

Function FloatRoundedToStr$ (fValue As _Float, intNumPlaces As Integer)
    Dim fNew As _Float
    fNew = Round##(fValue, intNumPlaces)
    FloatRoundedToStr$ = FloatToStr$(fNew)
End Function ' DblRoundedToStr$

' /////////////////////////////////////////////////////////////////////////////
' Scientific notation - QB64 Wiki
' https://www.qb64.org/wiki/Scientific_notation

' Example: A string function that displays extremely small or large exponential decimal values.

Function FloatToStr$ (n##)
    value$ = UCase$(LTrim$(Str$(n##)))
    Xpos% = InStr(value$, "D") + InStr(value$, "E") 'only D or E can be present
    If Xpos% Then
        expo% = Val(Mid$(value$, Xpos% + 1))
        If Val(value$) < 0 Then
            sign$ = "-"
            valu$ = Mid$(value$, 2, Xpos% - 2)
        Else
            valu$ = Mid$(value$, 1, Xpos% - 1)
        End If
        dot% = InStr(valu$, ".")
        L% = Len(valu$)
        If expo% > 0 Then
            add$ = String$(expo% - (L% - dot%), "0")
        End If
        If expo% < 0 Then
            min$ = String$(Abs(expo%) - (dot% - 1), "0")
            DP$ = "."
        End If
        For n = 1 To L%
            If Mid$(valu$, n, 1) <> "." Then
                num$ = num$ + Mid$(valu$, n, 1)
            End If
        Next n
    Else
        FloatToStr$ = value$
        Exit Function
    End If
    FloatToStr$ = _Trim$(sign$ + DP$ + min$ + num$ + add$)
End Function ' FloatToStr$

' /////////////////////////////////////////////////////////////////////////////
' TODO: find the newer formatting function?

Function FormatNumber$ (myValue, iDigits As Integer)
    Dim strValue As String
    strValue = DblToStr$(myValue) + String$(iDigits, " ")
    If myValue < 1 Then
        If myValue < 0 Then
            strValue = Replace$(strValue, "-.", "-0.")
        ElseIf myValue > 0 Then
            strValue = "0" + strValue
        End If
    End If
    FormatNumber$ = Left$(strValue, iDigits)
End Function ' FormatNumber$

' /////////////////////////////////////////////////////////////////////////////
' From: Bitwise Manipulations By Steven Roman
' http://www.romanpress.com/Articles/Bitwise_R/Bitwise.htm

' Returns the 8-bit binary representation
' of an integer iInput where 0 <= iInput <= 255

Function GetBinary$ (iInput1 As Integer)
    Dim sResult As String
    Dim iLoop As Integer
    Dim iInput As Integer: iInput = iInput1

    sResult = ""

    If iInput >= 0 And iInput <= 255 Then
        For iLoop = 1 To 8
            sResult = LTrim$(RTrim$(Str$(iInput Mod 2))) + sResult
            iInput = iInput \ 2
            'If iLoop = 4 Then sResult = " " + sResult
        Next iLoop
    End If

    GetBinary$ = sResult
End Function ' GetBinary$

' /////////////////////////////////////////////////////////////////////////////
' wonderfully inefficient way to read if a bit is set
' ival = GetBit256%(int we are comparing, int containing the bits we want to read)

' See also: GetBit256%, SetBit256%

Function GetBit256% (iNum1 As Integer, iBit1 As Integer)
    Dim iResult As Integer
    Dim sNum As String
    Dim sBit As String
    Dim iLoop As Integer
    Dim bContinue As Integer
    'DIM iTemp AS INTEGER
    Dim iNum As Integer: iNum = iNum1
    Dim iBit As Integer: iBit = iBit1

    iResult = FALSE
    bContinue = TRUE

    If iNum < 256 And iBit <= 128 Then
        sNum = GetBinary$(iNum)
        sBit = GetBinary$(iBit)
        For iLoop = 1 To 8
            If Mid$(sBit, iLoop, 1) = "1" Then
                'if any of the bits in iBit are false, return false
                If Mid$(sNum, iLoop, 1) = "0" Then
                    iResult = FALSE
                    bContinue = FALSE
                    Exit For
                End If
            End If
        Next iLoop
        If bContinue = TRUE Then
            iResult = TRUE
        End If
    End If

    GetBit256% = iResult
End Function ' GetBit256%

' /////////////////////////////////////////////////////////////////////////////
' Returns the text character at positon x%, y%

' Does the same as:
'   Locate y%, x%
'   GetCharXY% = Screen(CsrLin, Pos(0))

' See also: GetColorXY&

Function GetCharXY% (x%, y%)
    GetCharXY% = Screen(y%, x%, 0) ' when 3rd parameter = 0 returns character code
End Function ' GetCharXY%

' /////////////////////////////////////////////////////////////////////////////
' Returns the text color at positon x%, y%

' See also: GetCharXY%

Function GetColorXY& (x%, y%)
    GetColorXY& = Screen(y%, x%, 1) ' when 3rd parameter = 1 returns character color
End Function ' GetColorXY

' /////////////////////////////////////////////////////////////////////////////
' Simple timestamp function
' Format: {YYYY}-{MM}-{DD} {hh}:[mm}:{ss}

' Uses:
'     TIME$
'         The TIME$ Function returns a STRING representation
'         of the current computer time in a 24 hour format.
'         https://qb64phoenix.com/qb64wiki/index.php/TIME$
'     DATE$
'         The DATE$ function returns the current computer date
'         as a string in the format "mm-dd-yyyy".
'         https://qb64phoenix.com/qb64wiki/index.php/DATE$
'
' TODO: support template where
'       {yyyy} = 4 digit year
'       {mm}   = 2 digit month
'       {dd}   = 2 digit day
'       {hh}   = 2 digit hour (12-hour)
'       {rr}   = 2 digit hour (24-hour)
'       {nn}   = 2 digit minute
'       {ss}   = 2 digit second
'       {ampm} = AM/PM

' We got the nn for minute from Microsoft > Office VBA Reference > DateDiff function
' https://docs.microsoft.com/en-us/office/vba/language/reference/user-interface-help/datediff-function

' PRINT "Current date time (simple format) = " + Chr$(34) + GetCurrentDateTime$("{yyyy}-{mm}-{dd} {rr}:{nn}:{ss}") + Chr$(34)
' PRINT "Current date time (US format)     = " + Chr$(34) + GetCurrentDateTime$("{mm}/{dd}/{yyyy} {hh}:{nn}:{ss} {ampm}") + Chr$(34)
' PRINT "Filename timestamp                = " + Chr$(34) + GetCurrentDateTime$("{yyyy}{mm}{dd}_{rr}{nn}{ss}") + Chr$(34)

Function GetCurrentDateTime$ (sTemplate$)
    Dim sDate$: sDate$ = Date$
    Dim sTime$: sTime$ = Time$
    Dim sYYYY$: sYYYY$ = Mid$(sDate$, 7, 4)
    Dim sMM$: sMM$ = Mid$(sDate$, 1, 2)
    Dim sDD$: sDD$ = Mid$(sDate$, 4, 2)
    Dim sHH24$: sHH24$ = Mid$(sTime$, 1, 2)
    Dim sHH$: sHH$ = ""
    Dim sMI$: sMI$ = Mid$(sTime$, 4, 2)
    Dim sSS$: sSS$ = Mid$(sTime$, 7, 2)
    Dim iHour%: iHour% = Val(sHH24$)
    Dim sAMPM$: sAMPM$ = ""
    Dim result$: result$ = ""

    ' FIGURE OUT AM/PM
    If InStr(sTemplate$, "{ampm}") > 0 Then
        If iHour% = 0 Then
            sAMPM$ = "AM"
            iHour% = 12
        ElseIf iHour% > 0 And iHour% < 12 Then
            sAMPM$ = "AM"
        ElseIf iHour% = 12 Then
            sAMPM$ = "PM"
        Else
            sAMPM$ = "PM"
            iHour% = iHour% - 12
        End If
        sHH$ = Right$("00" + _Trim$(Str$(iHour%)), 2)
    End If

    ' POPULATE TEMPLATE
    result$ = sTemplate$
    result$ = Replace$(result$, "{yyyy}", sYYYY$)
    result$ = Replace$(result$, "{mm}", sMM$)
    result$ = Replace$(result$, "{dd}", sDD$)
    result$ = Replace$(result$, "{hh}", sHH$)
    result$ = Replace$(result$, "{rr}", sHH24$)
    result$ = Replace$(result$, "{nn}", sMI$)
    result$ = Replace$(result$, "{ss}", sSS$)
    result$ = Replace$(result$, "{ampm}", sAMPM$)

    ' RETURN RESULT
    GetCurrentDateTime$ = result$
End Function ' GetCurrentDateTime$

' /////////////////////////////////////////////////////////////////////////////
' From: Bitwise Manipulations By Steven Roman
' http://www.romanpress.com/Articles/Bitwise_R/Bitwise.htm

' Returns the integer that corresponds to a binary string of length 8

Function GetIntegerFromBinary% (sBinary1 As String)
    Dim iResult As Integer
    Dim iLoop As Integer
    Dim strBinary As String
    Dim sBinary As String: sBinary = sBinary1

    iResult = 0
    strBinary = Replace$(sBinary, " ", "") ' Remove any spaces
    For iLoop = 0 To Len(strBinary) - 1
        iResult = iResult + 2 ^ iLoop * Val(Mid$(strBinary, Len(strBinary) - iLoop, 1))
    Next iLoop

    GetIntegerFromBinary% = iResult
End Function ' GetIntegerFromBinary%

' /////////////////////////////////////////////////////////////////////////////
' Receives a {sDelimeter} delimited list of numbers {MyString}
' and splits it up into an integer array arrInteger()
' beginning at index {iMinIndex}.

Sub GetIntegerArrayFromDelimList (MyString As String, sDelimiter As String, iMinIndex As Integer, arrInteger() As Integer)
    ReDim arrString(-1) As String
    Dim CleanString As String
    Dim iLoop As Integer
    Dim iCount As Integer: iCount = iMinIndex - 1

    ReDim arrInteger(-1) As Integer

    'DebugPrint "GetIntegerArrayFromDelimList " + _
    '    "MyString=" + chr$(34) + MyString + chr$(34) + ", " + _
    '    "sDelimiter=" + chr$(34) + sDelimiter + chr$(34) + ", " + _
    '    "iMinIndex=" + cstr$(iMinIndex) + ", " + _
    '    "arrInteger()"


    If Len(sDelimiter) > 0 Then
        CleanString = MyString
        If sDelimiter <> " " Then
            CleanString = Replace$(CleanString, " ", "")
        End If

        split CleanString, sDelimiter, arrString()
        iCount = iMinIndex - 1
        For iLoop = LBound(arrString) To UBound(arrString)
            If IsNum%(arrString(iLoop)) = TRUE Then
                iCount = iCount + 1
                ReDim _Preserve arrInteger(iMinIndex To iCount) As Integer
                arrInteger(iCount) = Val(arrString(iLoop))
                'DebugPrint "5633 arrInteger(" + cstr$(iCount) + ") = VAL(arrString(" + cstr$(iLoop) + ")) = " + cstr$(arrInteger(iCount))

            End If
        Next iLoop
    Else
        If IsNum%(MyString) = TRUE Then
            ReDim _Preserve arrInteger(iMinIndex To iMinIndex) As Integer
            arrInteger(iMinIndex) = Val(MyString)
        End If
    End If

    'CleanString=""
    'for iLoop=lbound(arrInteger) to ubound(arrInteger)
    'CleanString = CleanString + iifstr$(iLoop=lbound(arrInteger), "", ",") + cstr$(arrInteger(iLoop))
    'next iLoop
    'DebugPrint "arrInteger=(" + CleanString + ")"
End Sub ' GetIntegerArrayFromDelimList

' /////////////////////////////////////////////////////////////////////////////
' IIF function for QB for integers

Function IIF (Condition, IfTrue, IfFalse)
    If Condition Then IIF = IfTrue Else IIF = IfFalse
End Function

' /////////////////////////////////////////////////////////////////////////////
' IIF function for QB for strings

Function IIFSTR$ (Condition, IfTrue$, IfFalse$)
    If Condition Then IIFSTR$ = IfTrue$ Else IIFSTR$ = IfFalse$
End Function

' /////////////////////////////////////////////////////////////////////////////

Function IntPadLeft$ (iValue As Integer, iWidth As Integer)
    IntPadLeft$ = Right$(String$(iWidth, " ") + _Trim$(Str$(iValue)), iWidth)
End Function ' IntPadLeft$

' /////////////////////////////////////////////////////////////////////////////

Function IntPadRight$ (iValue As Integer, iWidth As Integer)
    IntPadRight$ = Left$(_Trim$(Str$(iValue)) + String$(iWidth, " "), iWidth)
End Function ' IntPadRight$

' /////////////////////////////////////////////////////////////////////////////
' Returns TRUE if number n is even
' https://slaystudy.com/qbasic-program-to-check-if-number-is-even-or-odd/
' see also: IsOdd%

Function IsEven% (n)
    If n Mod 2 = 0 Then
        IsEven% = TRUE
    Else
        IsEven% = FALSE
    End If
End Function ' IsEven%

' /////////////////////////////////////////////////////////////////////////////
' Returns TRUE if value text$ is numeric.

Function IsNum% (text$)
    IsNum% = IsNumber%(text$)
End Function ' IsNum%

'' NOTE: THIS FAILS FOR NUMBERS LIKE "002" AND "2.000":
'' from https://www.qb64.org/forum/index.php?topic=896.0
'Function IsNum% (text$)
'    Dim a$
'    Dim b$
'    a$ = _Trim$(text$)
'    b$ = _Trim$(Str$(Val(text$)))
'    If a$ = b$ Then
'        IsNum% = TRUE
'    Else
'        IsNum% = FALSE
'    End If
'End Function ' IsNum%

' /////////////////////////////////////////////////////////////////////////////
' Returns TRUE if value OriginalString$ is numeric.

' Re: Does a Is Number function exist in QB64?
' https://www.qb64.org/forum/index.php?topic=896.15

' Version 2 by madscijr
' Returns TRUE (-1) if string is an integer, FALSE (0) if not

' Version 1 by MWheatley
' Reply #18 on: January 01, 2019, 11:24:30 AM
' returns 1 if string is an integer, 0 if not

Function IsNumber% (OriginalString$)
    Dim bResult%: bResult% = FALSE
    Dim iLoop%
    Dim TestString$
    'Dim bNegative%
    Dim iDecimalCount%
    Dim sNextChar$
   
    'THEY SHOULD TRIM OUTSIDE THE FUNCTION!
    'TestString$ = _TRIM$(OriginalString$)
   
    If Len(OriginalString$) > 0 Then
        TestString$ = ""
        If Left$(OriginalString$, 1) = "+" Then
            TestString$ = Right$(OriginalString$, Len(OriginalString$) - 1)
            'bNegative% = FALSE
        ElseIf Left$(OriginalString$, 1) = "-" Then
            TestString$ = Right$(OriginalString$, Len(OriginalString$) - 1)
            'bNegative% = TRUE
        Else
            TestString$ = OriginalString$
            'bNegative% = FALSE
        End If
        If Len(TestString$) > 0 Then
            bResult% = TRUE
            iDecimalCount% = 0
            For iLoop% = 1 To Len(TestString$)
                sNextChar$ = Mid$(TestString$, iLoop%, 1)
                If sNextChar$ = "." Then
                    iDecimalCount% = iDecimalCount% + 1
                    If iDecimalCount% > 1 Then
                        ' TOO MANY DECIMAL POINTS, INVALID!
                        bResult% = FALSE
                        Exit For
                    End If
                ElseIf Asc(sNextChar$) < 48 Or Asc(sNextChar$) > 57 Then
                    ' NOT A NUMERAL OR A DECIMAL, INVALID!
                    bResult% = FALSE
                    Exit For
                End If
            Next iLoop%
        End If
    End If
    IsNumber% = bResult%
End Function ' IsNumber%

' /////////////////////////////////////////////////////////////////////////////

'Sub IsNumberTest
'    Dim in$
'    Cls
'    IsNumberTest1 "1"
'    IsNumberTest1 "01"
'    IsNumberTest1 "001"
'    IsNumberTest1 "-1"
'    IsNumberTest1 "-01"
'    IsNumberTest1 "-001"
'    IsNumberTest1 "+1"
'    IsNumberTest1 "+01"
'    IsNumberTest1 "+001"
'    IsNumberTest1 ".1"
'    IsNumberTest1 ".01"
'    IsNumberTest1 ".001"
'    IsNumberTest1 ".10"
'    IsNumberTest1 ".100"
'    IsNumberTest1 "..100"
'    IsNumberTest1 "100."
'    Input "PRESS ENTER TO CONTINUE TEST";in$
'    Cls
'    IsNumberTest1 "0.10"
'    IsNumberTest1 "00.100"
'    IsNumberTest1 "000.1000"
'    IsNumberTest1 "000..1000"
'    IsNumberTest1 "000.1000.00"
'    IsNumberTest1 "+1.00"
'    IsNumberTest1 "++1.00"
'    IsNumberTest1 "+-1.00"
'    IsNumberTest1 "-1.00"
'    IsNumberTest1 "-+1.00"
'    IsNumberTest1 " 1"
'    IsNumberTest1 "1 "
'    IsNumberTest1 "1. 01"
'    IsNumberTest1 "+1 "
'End Sub ' IsNumberTest
'Sub IsNumberTest1(MyString As String)
'    Const cWidth = 16
'    Dim sInput As String : sInput = left$(Chr$(34) + MyString + Chr$(34) + String$(cWidth, " "), cWidth)
'    Dim sResult As String : sResult = right$(String$(2, " ") + _Trim$(Str$(IsNumber%(MyString))), 2)
'    Print "IsNumber%(" + sInput + ") returns " + sResult
'End Sub ' IsNumberTest1

' /////////////////////////////////////////////////////////////////////////////
' Returns TRUE if number n is odd
' https://slaystudy.com/qbasic-program-to-check-if-number-is-even-or-odd/
' see also: IsEven%

Function IsOdd% (n)
    If n Mod 2 = 1 Then
        IsOdd% = TRUE
    Else
        IsOdd% = FALSE
    End If
End Function ' IsOdd%

' /////////////////////////////////////////////////////////////////////////////
' Split and join strings
' https://www.qb64.org/forum/index.php?topic=1073.0

'Combine all elements of in$() into a single string with delimiter$ separating the elements.

Function join$ (in$(), delimiter$)
    Dim result$
    Dim i As Long
    result$ = in$(LBound(in$))
    For i = LBound(in$) + 1 To UBound(in$)
        result$ = result$ + delimiter$ + in$(i)
    Next i
    join$ = result$
End Function ' join$

' /////////////////////////////////////////////////////////////////////////////

Function LeftPadString$ (myString$, toWidth%, padChar$)
    LeftPadString$ = Right$(String$(toWidth%, padChar$) + myString$, toWidth%)
End Function ' LeftPadString$

' /////////////////////////////////////////////////////////////////////////////
' ABS was returning strange values with type LONG
' so I created this which does not.

Function LongABS& (lngValue As Long)
    If Sgn(lngValue) = -1 Then
        LongABS& = 0 - lngValue
    Else
        LongABS& = lngValue
    End If
End Function ' LongABS&

' /////////////////////////////////////////////////////////////////////////////
' remove scientific Notation to String (~40 LOC)
' SMcNeill Jan 7, 2020
' https://www.qb64.org/forum/index.php?topic=1555.msg112989#msg112989

' Last Function in code marked Best Answer (removed debug comments and
' blank lines added these 2 lines.)

Function N2S$ (EXP$)
    ReDim t$, sign$, l$, r$, r&&
    ReDim dp As Long, dm As Long, ep As Long, em As Long, check1 As Long, l As Long, i As Long
    t$ = LTrim$(RTrim$(EXP$))
    If Left$(t$, 1) = "-" Or Left$(t$, 1) = "N" Then sign$ = "-": t$ = Mid$(t$, 2)
    dp = InStr(t$, "D+"): dm = InStr(t$, "D-")
    ep = InStr(t$, "E+"): em = InStr(t$, "E-")
    check1 = Sgn(dp) + Sgn(dm) + Sgn(ep) + Sgn(em)
    If check1 < 1 Or check1 > 1 Then N2S = _Trim$(EXP$): Exit Function ' If no scientic notation is found, or if we find more than 1 type, it's not SN!
    Select Case l ' l now tells us where the SN starts at.
        Case Is < dp: l = dp
        Case Is < dm: l = dm
        Case Is < ep: l = ep
        Case Is < em: l = em
    End Select
    l$ = Left$(t$, l - 1) ' The left of the SN
    r$ = Mid$(t$, l + 1): r&& = Val(r$) ' The right of the SN, turned into a workable long
    If InStr(l$, ".") Then ' Location of the decimal, if any
        If r&& > 0 Then
            r&& = r&& - Len(l$) + 2
        Else
            r&& = r&& + 1
        End If
        l$ = Left$(l$, 1) + Mid$(l$, 3)
    End If
    Select Case r&&
        Case 0 ' what the heck? We solved it already?
            ' l$ = l$
        Case Is < 0
            For i = 1 To -r&&
                l$ = "0" + l$
            Next
            l$ = "." + l$
        Case Else
            For i = 1 To r&&
                l$ = l$ + "0"
            Next
            l$ = l$
    End Select
    N2S$ = sign$ + l$
End Function ' N2S$

' /////////////////////////////////////////////////////////////////////////////
' Pauses for iDS deciseconds (iDS * 100 ms)

Sub PauseDecisecond (iDS As Integer)
    Dim iCount As Integer
    iCount = 0
    Do
        iCount = iCount + 1
        _Limit 10 ' run 10x every second
    Loop Until iCount = iDS
End Sub ' PauseDecisecond

' /////////////////////////////////////////////////////////////////////////////
' Returns TRUE if point (x1%, y1%) is adjacent to point (x2%, y2%)

Function PointsAreAdjacent% (x1%, y1%, x2%, y2%)
    Dim bResult%: bResult% = FALSE

    ' x or y can be the same, but not both
    If (x1% <> x2%) Or (y1% <> y2%) Then
        If (x1% = x2%) Or ((x1% = (x2% + 1)) Or (x2% = (x1% + 1))) Then
            If (y1% = y2%) Or ((y1% = (y2% + 1)) Or (y2% = (y1% + 1))) Then
                bResult% = TRUE
            End If
        End If
    End If
    PointsAreAdjacent% = bResult%
End Function ' PointsAreAdjacent%

' /////////////////////////////////////////////////////////////////////////////
' Does a _PrintString at the specified row+column.
'
'
' iRow% and iCol% are 0-based in this version
'
' See also: PrintString, PrintString1, PutCharXY

Sub PrintAt (iRow%, iCol%, sText$)
    '_PrintString (iCol% * 8, iRow% * 16), sText$
    _PrintString (iCol% * 8, iRow% * 16), sText$
    '_PrintString (iCol%, iRow%), sText$
End Sub ' PrintAt

' /////////////////////////////////////////////////////////////////////////////
' Writes sText to file sFileName.
' If bAppend=TRUE appends to file, else overwrites it.

' Returns blank if successful else returns error message.

' Example:
' ProgramPath$ = Left$(Command$(0), _InStrRev(Command$(0), "\"))
' ProgramName$: m_ProgramName$ = Mid$(Command$(0), _InStrRev(Command$(0), "\") + 1)
' sFileName = ProgramPath$ + ProgramName$ + ".OUT.txt"
' sText = "This is a test." + chr$(13) + "Here is line 2." + chr$(13) + "End."
' sError = PrintFile$(sFileName, sText, FALSE)

Function PrintFile$ (sFileName As String, sText As String, bAppend As Integer)
    Dim sError As String: sError = ""

    If Len(sError) = 0 Then
        If (bAppend = TRUE) Then
            If _FileExists(sFileName) Then
                Open sFileName For Append As #1 ' opens an existing file for appending
            Else
                sError = "Error in PrintFile$ : File not found. Cannot append."
            End If
        Else
            Open sFileName For Output As #1 ' opens and clears an existing file or creates new empty file
        End If
    End If
    If Len(sError) = 0 Then
        ' NOTE: WRITE places text in quotes in the file
        'WRITE #1, x, y, z$
        'WRITE #1, sText

        ' PRINT does not put text inside quotes
        Print #1, sText

        Close #1
    End If

    PrintFile$ = sError
End Function ' PrintFile$

' /////////////////////////////////////////////////////////////////////////////
' Does a _PrintString at the specified row+column.
' iRow and iCol are 0-based.
' See also: PrintString1

Sub PrintString (iRow As Integer, iCol As Integer, MyString As String)
    Dim iX As Integer
    Dim iY As Integer
    iX = _FontWidth * iCol
    iY = _FontHeight * iRow ' (iRow + 1)
    _PrintString (iX, iY), MyString
End Sub ' PrintString

' /////////////////////////////////////////////////////////////////////////////
' Does a _PrintString at the specified row+column.
' iRow and iCol are 1-based.
' See also: PrintString

Sub PrintString1 (iRow As Integer, iCol As Integer, MyString As String)
    Dim iX As Integer
    Dim iY As Integer
    iX = _FontWidth * (iCol - 1)
    iY = _FontHeight * (iRow - 1)
    _PrintString (iX, iY), MyString
End Sub ' PrintString1

' /////////////////////////////////////////////////////////////////////////////
' Prints text character char$ at positoin x%,y% in color myColor&.

Sub PutCharXY (x%, y%, char$, myColor&)
    Color myColor&
    Locate y%, x%
    Print char$;
End Sub ' PutCharXY

' /////////////////////////////////////////////////////////////////////////////
' Generate random value between Min and Max inclusive.

Function RandomNumber% (Min%, Max%)
    Dim NumSpread%

    ' SET RANDOM SEED
    'Randomize ' Initialize random-number generator.
    Randomize Timer

    ' GET RANDOM # Min%-Max%
    'RandomNumber = Int((Max * Rnd) + Min) ' generate number

    NumSpread% = (Max% - Min%) + 1

    RandomNumber% = Int(Rnd * NumSpread%) + Min% ' GET RANDOM # BETWEEN Max% AND Min%

End Function ' RandomNumber%

'' /////////////////////////////////////////////////////////////////////////////
'
'Sub RandomNumberTest
'    Dim iCols As Integer: iCols = 10
'    Dim iRows As Integer: iRows = 20
'    Dim iLoop As Integer
'    Dim iX As Integer
'    Dim iY As Integer
'    Dim sError As String
'    Dim sFileName As String
'    Dim sText As String
'    Dim bAppend As Integer
'    Dim iMin As Integer
'    Dim iMax As Integer
'    Dim iNum As Integer
'    Dim iErrorCount As Integer
'    Dim sInput$
'
'    sFileName = "c:\temp\maze_test_1.txt"
'    sText = "Count" + Chr$(9) + "Min" + Chr$(9) + "Max" + Chr$(9) + "Random"
'    bAppend = FALSE
'    sError = PrintFile$(sFileName, sText, bAppend)
'    If Len(sError) = 0 Then
'        bAppend = TRUE
'        iErrorCount = 0
'
'        iMin = 0
'        iMax = iCols - 1
'        For iLoop = 1 To 100
'            iNum = RandomNumber%(iMin, iMax)
'            sText = Str$(iLoop) + Chr$(9) + Str$(iMin) + Chr$(9) + Str$(iMax) + Chr$(9) + Str$(iNum)
'            sError = PrintFile$(sFileName, sText, bAppend)
'            If Len(sError) > 0 Then
'                iErrorCount = iErrorCount + 1
'                Print Str$(iLoop) + ". ERROR"
'                Print "    " + "iMin=" + Str$(iMin)
'                Print "    " + "iMax=" + Str$(iMax)
'                Print "    " + "iNum=" + Str$(iNum)
'                Print "    " + "Could not write to file " + Chr$(34) + sFileName + Chr$(34) + "."
'                Print "    " + sError
'            End If
'        Next iLoop
'
'        iMin = 0
'        iMax = iRows - 1
'        For iLoop = 1 To 100
'            iNum = RandomNumber%(iMin, iMax)
'            sText = Str$(iLoop) + Chr$(9) + Str$(iMin) + Chr$(9) + Str$(iMax) + Chr$(9) + Str$(iNum)
'            sError = PrintFile$(sFileName, sText, bAppend)
'            If Len(sError) > 0 Then
'                iErrorCount = iErrorCount + 1
'                Print Str$(iLoop) + ". ERROR"
'                Print "    " + "iMin=" + Str$(iMin)
'                Print "    " + "iMax=" + Str$(iMax)
'                Print "    " + "iNum=" + Str$(iNum)
'                Print "    " + "Could not write to file " + Chr$(34) + sFileName + Chr$(34) + "."
'                Print "    " + sError
'            End If
'        Next iLoop
'
'        Print "Finished generating numbers. Errors: " + Str$(iErrorCount)
'    Else
'        Print "Error creating file " + Chr$(34) + sFileName + Chr$(34) + "."
'        Print sError
'    End If
'
'    Input "Press <ENTER> to continue", sInput$
'End Sub ' RandomNumberTest

' /////////////////////////////////////////////////////////////////////////////
' FROM: String Manipulation
' found at abandoned, outdated and now likely malicious qb64 dot net website
' http://www.qb64.[net]/forum/index_topic_5964-0/
'
'SUMMARY:
'   Purpose:  A library of custom functions that transform strings.
'   Author:   Dustinian Camburides (dustinian@gmail.com)
'   Platform: QB64 (www.qb64.org)
'   Revision: 1.6
'   Updated:  5/28/2012

'SUMMARY:
'[Replace$] replaces all instances of the [Find] sub-string with the [Add] sub-string within the [Text] string.
'INPUT:
'Text: The input string; the text that's being manipulated.
'Find: The specified sub-string; the string sought within the [Text] string.
'Add: The sub-string that's being added to the [Text] string.

Function Replace$ (Text1 As String, Find1 As String, Add1 As String)
    ' VARIABLES:
    Dim Text2 As String
    Dim Find2 As String
    Dim Add2 As String
    Dim lngLocation As Long ' The address of the [Find] substring within the [Text] string.
    Dim strBefore As String ' The characters before the string to be replaced.
    Dim strAfter As String ' The characters after the string to be replaced.

    ' INITIALIZE:
    ' MAKE COPIESSO THE ORIGINAL IS NOT MODIFIED (LIKE ByVal IN VBA)
    Text2 = Text1
    Find2 = Find1
    Add2 = Add1

    lngLocation = InStr(1, Text2, Find2)

    ' PROCESSING:
    ' While [Find2] appears in [Text2]...
    While lngLocation
        ' Extract all Text2 before the [Find2] substring:
        strBefore = Left$(Text2, lngLocation - 1)

        ' Extract all text after the [Find2] substring:
        strAfter = Right$(Text2, ((Len(Text2) - (lngLocation + Len(Find2) - 1))))

        ' Return the substring:
        Text2 = strBefore + Add2 + strAfter

        ' Locate the next instance of [Find2]:
        lngLocation = InStr(1, Text2, Find2)

        ' Next instance of [Find2]...
    Wend

    ' OUTPUT:
    Replace$ = Text2
End Function ' Replace$

'' /////////////////////////////////////////////////////////////////////////////
'
'Sub ReplaceTest
'    Dim in$
'
'    Print "-------------------------------------------------------------------------------"
'    Print "ReplaceTest"
'    Print
'
'    Print "Original value"
'    in$ = "Thiz iz a teZt."
'    Print "in$ = " + Chr$(34) + in$ + Chr$(34)
'    Print
'
'    Print "Replacing lowercase " + Chr$(34) + "z" + Chr$(34) + " with " + Chr$(34) + "s" + Chr$(34) + "..."
'    in$ = Replace$(in$, "z", "s")
'    Print "in$ = " + Chr$(34) + in$ + Chr$(34)
'    Print
'
'    Print "Replacing uppercase " + Chr$(34) + "Z" + Chr$(34) + " with " + Chr$(34) + "s" + Chr$(34) + "..."
'    in$ = Replace$(in$, "Z", "s")
'    Print "in$ = " + Chr$(34) + in$ + Chr$(34)
'    Print
'
'    Print "ReplaceTest finished."
'End Sub ' ReplaceTest

' /////////////////////////////////////////////////////////////////////////////

Function RightPadString$ (myString$, toWidth%, padChar$)
    RightPadString$ = Left$(myString$ + String$(toWidth%, padChar$), toWidth%)
End Function ' RightPadString$

'' /////////////////////////////////////////////////////////////////////////////
'' https://staging.qb64phoenix.com/showthread.php?tid=644
'' From: bplus
'' Date: 07-18-2022, 03:16 PM
'' Here is a Round$ that acts the way you'd expect in under 100 LOC
'' b = b + ...
'
'Function Round$ (anyNumber, dp As Long)
'    ' 5 and up at decimal place dp+1 > +1 at decimal place   4 and down  > +0 at dp
'    ' 2 1 0.-1 -2 -3 -4 ...  pick dp like this for this Round$ Function
'    sn$ = N2S$(Str$(anyNumber + .5 * 10 ^ dp)) ' get rid of sci notation, steve trims it so next find dot
'    dot = InStr(sn$, ".")
'    If dot Then
'        predot = dot - 1
'        postdot = Len(sn$) - (dot + 1)
'    Else
'        predot = Len(sn$)
'        postdot = 0
'    End If
'    ' xxx.yyyyyy  dp = -2
'    '      ^ dp
'    If dp >= 0 Then
'        Rtn$ = Mid$(sn$, 1, predot - dp) + String$(dp, "0")
'    Else
'        Rtn$ = Mid$(sn$, 1, predot) + "." + Mid$(sn$, dot + 1, -dp)
'    End If
'    If Rtn$ = "" Then
'        Round$ = "0"
'    Else
'        Round$ = Rtn$
'    End If
'End Function ' Round$
'
''' /////////////////////////////////////////////////////////////////////////////
''
''Sub RoundTest
''   Print Round$(.15, 0) '  0
''   Print Round$(.15, -1) ' .2
''   Print Round$(.15, -2) ' .15
''   Print Round$(.15, -3) ' .150
''   Print
''   Print Round$(3555, 0) ' 3555
''   Print Round$(3555, 1) ' 3560
''   Print Round$(3555, 2) ' 3600 'good
''   Print Round$(3555, 3) ' 4000
''   Print
''   Print Round$(23.149999, -1) ' 23.1
''   Print Round$(23.149999, -2) ' 23.15
''   Print Round$(23.149999, -3) ' 23.150
''   Print Round$(23.149999, -4) ' 23.1500
''   Print
''   Print Round$(23.143335, -1) ' 23.1 OK?
''   Print Round$(23.143335, -2) ' 23.14
''   Print Round$(23.143335, -3) ' 23.143
''   Print Round$(23.143335, -4) ' 23.1433
''   Print Round$(23.143335, -5) ' 23.14334
''   Print
''   Dim float31 As _Float
''   float31 = .310000000000009
''   Print Round$(.31, -2) ' .31
''   Print Round$(.31##, -2)
''   Print Round$(float31, -2)
''End Sub ' RoundTest

' /////////////////////////////////////////////////////////////////////////////
' https://www.qb64.org/forum/index.php?topic=3605.0
' Quote from: SMcNeill on Today at 03:53:48 PM
'
' Sometimes, you guys make things entirely too  complicated.
' There ya go!  Three functions to either round naturally,
' always round down, or always round up, to whatever number of digits you desire.
' EDIT:  Modified to add another option to round scientific,
' since you had it's description included in your example.

' Receives + returns _FLOAT myVar## (-1.18E-4932 to +1.18E+4932)

' =============================================================================
' ROUNDING FUNCTIONS FOR TYPE _FLOAT

' old name: RoundNatural##
Function Round## (num##, digits%)
    Round## = Int(num## * 10 ^ digits% + .5) / 10 ^ digits%
End Function

Function RoundUp## (num##, digits%)
    RoundUp## = _Ceil(num## * 10 ^ digits%) / 10 ^ digits%
End Function

Function RoundDown## (num##, digits%)
    RoundDown## = Int(num## * 10 ^ digits%) / 10 ^ digits%
End Function

' old name: Round_Scientific##
Function RoundScientific## (num##, digits%)
    RoundScientific## = _Round(num## * 10 ^ digits%) / 10 ^ digits%
End Function

' =============================================================================
' ROUNDING FUNCTIONS FOR TYPE DOUBLE

Function RoundDouble# (num#, digits%)
    RoundDouble# = Int(num# * 10 ^ digits% + .5) / 10 ^ digits%
End Function

Function RoundUpDouble# (num#, digits%)
    RoundUpDouble# = _Ceil(num# * 10 ^ digits%) / 10 ^ digits%
End Function

Function RoundDownDouble# (num#, digits%)
    RoundDownDouble# = Int(num# * 10 ^ digits%) / 10 ^ digits%
End Function

Function RoundScientificDouble# (num#, digits%)
    RoundScientificDouble# = _Round(num# * 10 ^ digits%) / 10 ^ digits%
End Function

' =============================================================================
' ROUNDING FUNCTIONS FOR TYPE SINGLE

Function RoundSingle! (num!, digits%)
    RoundSingle! = Int(num! * 10 ^ digits% + .5) / 10 ^ digits%
End Function

' NOTE: not sure this one works: when digits%=3, it rounds .31 to .32
Function RoundUpSingle! (num!, digits%)
    RoundUpSingle! = _Ceil(num! * 10 ^ digits%) / 10 ^ digits%
End Function

Function RoundDownSingle! (num!, digits%)
    RoundDownSingle! = Int(num! * 10 ^ digits%) / 10 ^ digits%
End Function

Function RoundScientificSingle! (num!, digits%)
    RoundScientificSingle! = _Round(num! * 10 ^ digits%) / 10 ^ digits%
End Function

' /////////////////////////////////////////////////////////////////////////////
' fantastically inefficient way to set a bit

' example use: arrMaze(iX, iY) = SetBit256%(arrMaze(iX, iY), cS, FALSE)

' See also: GetBit256%, SetBit256%

' newint=SetBit256%(oldint, int containing the bits we want to set, value to set them to)
Function SetBit256% (iNum1 As Integer, iBit1 As Integer, bVal1 As Integer)
    Dim sNum As String
    Dim sBit As String
    Dim sVal As String
    Dim iLoop As Integer
    Dim strResult As String
    Dim iResult As Integer
    Dim iNum As Integer: iNum = iNum1
    Dim iBit As Integer: iBit = iBit1
    Dim bVal As Integer: bVal = bVal1

    If iNum < 256 And iBit <= 128 Then
        sNum = GetBinary$(iNum)
        sBit = GetBinary$(iBit)
        If bVal = TRUE Then
            sVal = "1"
        Else
            sVal = "0"
        End If
        strResult = ""
        For iLoop = 1 To 8
            If Mid$(sBit, iLoop, 1) = "1" Then
                strResult = strResult + sVal
            Else
                strResult = strResult + Mid$(sNum, iLoop, 1)
            End If
        Next iLoop
        iResult = GetIntegerFromBinary%(strResult)
    Else
        iResult = iNum
    End If

    SetBit256% = iResult
End Function ' SetBit256%

' /////////////////////////////////////////////////////////////////////////////

Function SmallestOf3% (i1%, i2%, i3%)
    Dim iMin%
    iMin% = i1%
    If i2% < iMin% Then iMin% = i2%
    If i3% < iMin% Then iMin% = i3%
    SmallestOf3% = iMin%
End Function ' SmallestOf3

' /////////////////////////////////////////////////////////////////////////////

Function SngRoundedToStr$ (sngValue As Single, intNumPlaces As Integer)
    Dim sngNew As Single
    sngNew = RoundSingle!(sngValue, intNumPlaces)
    SngRoundedToStr$ = SngToStr$(sngNew)
End Function ' SngRoundedToStr$

' /////////////////////////////////////////////////////////////////////////////
' Hack function, to be replaced with something better

Function SngToInt% (sngOld As Single)
    Dim sngNew As Single
    Dim sValue As String
    Dim iPos As Integer

    sngNew = RoundSingle!(sngOld, 0)
    'sValue = _Trim$(Str$(sngNew))

    sValue = SngToStr$(sngNew)

    'iPos = InStr(1, sValue, ".")
    'If iPos > 0 Then
    '    SngToInt% = Val(Left$(sValue, iPos - 1))
    'Else
    '    SngToInt% = Val(sValue)
    'End If

    SngToInt% = Val(sValue)
End Function ' SngToInt%

' /////////////////////////////////////////////////////////////////////////////
' Scientific notation - QB64 Wiki
' https://www.qb64.org/wiki/Scientific_notation

' Example: A string function that displays extremely small or large exponential decimal values.

Function SngToStr$ (n!)
    Dim result$: result$ = ""
    Dim value$
    Dim Xpos%
    Dim expo%
    Dim sign$
    Dim valu$
    Dim dot%
    Dim L%
    Dim add$
    Dim min$
    Dim DP$
    Dim n%

    value$ = UCase$(LTrim$(Str$(n!)))
    Xpos% = InStr(value$, "D") + InStr(value$, "E") ' only D or E can be present
    If Xpos% Then
        expo% = Val(Mid$(value$, Xpos% + 1))
        If Val(value$) < 0 Then
            sign$ = "-"
            valu$ = Mid$(value$, 2, Xpos% - 2)
        Else
            valu$ = Mid$(value$, 1, Xpos% - 1)
        End If
        dot% = InStr(valu$, ".")
        L% = Len(valu$)
        If expo% > 0 Then
            add$ = String$(expo% - (L% - dot%), "0")
        End If
        If expo% < 0 Then
            min$ = String$(Abs(expo%) - (dot% - 1), "0")
            DP$ = "."
        End If
        For n% = 1 To L%
            If Mid$(valu$, n%, 1) <> "." Then
                num$ = num$ + Mid$(valu$, n%, 1)
            End If
        Next n%
        result$ = _Trim$(sign$ + DP$ + min$ + num$ + add$)
    Else
        result$ = value$
    End If

    SngToStr$ = result$
End Function ' SngToStr$

' /////////////////////////////////////////////////////////////////////////////
' Split and join strings
' https://www.qb64.org/forum/index.php?topic=1073.0
'
' FROM luke, QB64 Developer
' Date: February 15, 2019, 04:11:07 AM
'
' Given a string of words separated by spaces (or any other character),
' splits it into an array of the words. I've no doubt many people have
' written a version of this over the years and no doubt there's a million
' ways to do it, but I thought I'd put mine here so we have at least one
' version. There's also a join function that does the opposite
' array -> single string.
'
' Code is hopefully reasonably self explanatory with comments and a little demo.
' Note, this is akin to Python/JavaScript split/join, PHP explode/implode.

'Split in$ into pieces, chopping at every occurrence of delimiter$. Multiple consecutive occurrences
'of delimiter$ are treated as a single instance. The chopped pieces are stored in result$().
'
'delimiter$ must be one character long.
'result$() must have been REDIMmed previously.

' Modified to handle multi-character delimiters

Sub split (in$, delimiter$, result$())
    Dim start As Integer
    Dim finish As Integer
    Dim iDelimLen As Integer
    ReDim result$(-1)

    iDelimLen = Len(delimiter$)

    start = 1
    Do
        'While Mid$(in$, start, 1) = delimiter$
        While Mid$(in$, start, iDelimLen) = delimiter$
            'start = start + 1
            start = start + iDelimLen
            If start > Len(in$) Then
                Exit Sub
            End If
        Wend
        finish = InStr(start, in$, delimiter$)
        If finish = 0 Then
            finish = Len(in$) + 1
        End If

        ReDim _Preserve result$(0 To UBound(result$) + 1)

        result$(UBound(result$)) = Mid$(in$, start, finish - start)
        start = finish + 1
    Loop While start <= Len(in$)
End Sub ' split

'' /////////////////////////////////////////////////////////////////////////////
'
'Sub SplitTest
'    Dim in$
'    Dim delim$
'    ReDim arrTest$(0)
'    Dim iLoop%
'
'    delim$ = Chr$(10)
'    in$ = "this" + delim$ + "is" + delim$ + "a" + delim$ + "test"
'    Print "in$ = " + Chr$(34) + in$ + Chr$(34)
'    Print "delim$ = " + Chr$(34) + delim$ + Chr$(34)
'    split in$, delim$, arrTest$()
'
'    For iLoop% = LBound(arrTest$) To UBound(arrTest$)
'        Print "arrTest$(" + LTrim$(RTrim$(Str$(iLoop%))) + ") = " + Chr$(34) + arrTest$(iLoop%) + Chr$(34)
'    Next iLoop%
'    Print
'    Print "Split test finished."
'End Sub ' SplitTest

'' /////////////////////////////////////////////////////////////////////////////
'
'Sub SplitAndReplaceTest
'    Dim in$
'    Dim out$
'    Dim iLoop%
'    ReDim arrTest$(0)
'
'    Print "-------------------------------------------------------------------------------"
'    Print "SplitAndReplaceTest"
'    Print
'
'    Print "Original value"
'    in$ = "This line 1 " + Chr$(13) + Chr$(10) + "and line 2" + Chr$(10) + "and line 3 " + Chr$(13) + "finally THE END."
'    out$ = in$
'    out$ = Replace$(out$, Chr$(13), "\r")
'    out$ = Replace$(out$, Chr$(10), "\n")
'    out$ = Replace$(out$, Chr$(9), "\t")
'    Print "in$ = " + Chr$(34) + out$ + Chr$(34)
'    Print
'
'    Print "Fixing linebreaks..."
'    in$ = Replace$(in$, Chr$(13) + Chr$(10), Chr$(13))
'    in$ = Replace$(in$, Chr$(10), Chr$(13))
'    out$ = in$
'    out$ = Replace$(out$, Chr$(13), "\r")
'    out$ = Replace$(out$, Chr$(10), "\n")
'    out$ = Replace$(out$, Chr$(9), "\t")
'    Print "in$ = " + Chr$(34) + out$ + Chr$(34)
'    Print
'
'    Print "Splitting up..."
'    split in$, Chr$(13), arrTest$()
'
'    For iLoop% = LBound(arrTest$) To UBound(arrTest$)
'        out$ = arrTest$(iLoop%)
'        out$ = Replace$(out$, Chr$(13), "\r")
'        out$ = Replace$(out$, Chr$(10), "\n")
'        out$ = Replace$(out$, Chr$(9), "\t")
'        Print "arrTest$(" + cstr$(iLoop%) + ") = " + Chr$(34) + out$ + Chr$(34)
'    Next iLoop%
'    Print
'
'    Print "SplitAndReplaceTest finished."
'End Sub ' SplitAndReplaceTest

' /////////////////////////////////////////////////////////////////////////////
' Converts a chr$(13) delimited string
' into a 2-dimensional array.

' Usage:
' Dim StringArray(1 To 48, 1 To 128) As String
' StringTo2dArray StringArray(), GetMap$

' Version 2 with indexed array(row, columm)
' Renamed StringToArray to StringTo2dArray.

' See also: Array2dToString$

Sub StringTo2dArray (MyArray() As String, MyString As String)
    Dim sDelim As String
    ReDim arrLines(0) As String
    Dim iRow As Integer
    Dim iCol As Integer
    Dim sChar As String
    Dim iDim1 As Integer
    Dim iDim2 As Integer
    Dim iIndex1 As Integer
    Dim iIndex2 As Integer

    iDim1 = LBound(MyArray, 1)
    iDim2 = LBound(MyArray, 2)
    sDelim = Chr$(13)
    split MyString, sDelim, arrLines()
    For iRow = LBound(arrLines) To UBound(arrLines)
        If iRow <= UBound(MyArray, 1) Then
            For iCol = 1 To Len(arrLines(iRow))
                If iCol <= UBound(MyArray, 2) Then
                    sChar = Mid$(arrLines(iRow), iCol, 1)

                    If Len(sChar) > 1 Then
                        sChar = Left$(sChar, 1)
                    Else
                        If Len(sChar) = 0 Then
                            sChar = "."
                        End If
                    End If

                    iIndex1 = iRow + iDim1
                    iIndex2 = (iCol - 1) + iDim2
                    MyArray(iIndex1, iIndex2) = sChar
                    'DebugPrint "MyArray(" + cstr$(iIndex1) + ", " + cstr$(iIndex2) + " = " + chr$(34) + sChar + chr$(34)
                Else
                    ' Exit if out of bounds
                    Exit For
                End If
            Next iCol
        Else
            ' Exit if out of bounds
            Exit For
        End If
    Next iRow
End Sub ' StringTo2dArray

' /////////////////////////////////////////////////////////////////////////////

Function StrPadLeft$ (sValue As String, iWidth As Integer)
    StrPadLeft$ = Right$(String$(iWidth, " ") + sValue, iWidth)
End Function ' StrPadLeft$

' /////////////////////////////////////////////////////////////////////////////

Function StrJustifyRight$ (sValue As String, iWidth As Integer)
    StrJustifyRight$ = Right$(String$(iWidth, " ") + sValue, iWidth)
End Function ' StrJustifyRight$

' /////////////////////////////////////////////////////////////////////////////

Function StrPadRight$ (sValue As String, iWidth As Integer)
    StrPadRight$ = Left$(sValue + String$(iWidth, " "), iWidth)
End Function ' StrPadRight$

' /////////////////////////////////////////////////////////////////////////////

Function StrJustifyLeft$ (sValue As String, iWidth As Integer)
    StrJustifyLeft$ = Left$(sValue + String$(iWidth, " "), iWidth)
End Function ' StrJustifyLeft$

' /////////////////////////////////////////////////////////////////////////////
' div: int1% = num1% \ den1%
' mod: rem1% = num1% MOD den1%

Function StrJustifyCenter$ (sValue As String, iWidth As Integer)
    Dim iLen0 As Integer
    Dim iLen1 As Integer
    Dim iLen2 As Integer
    Dim iExtra As Integer

    iLen0 = Len(sValue)
    If iWidth = iLen0 Then
        ' no extra space: return unchanged
        StrJustifyCenter$ = sValue
    ElseIf iWidth > iLen0 Then
        If IsOdd%(iWidth) Then
            iWidth = iWidth - 1
        End If

        ' center
        iExtra = iWidth - iLen0
        iLen1 = iExtra \ 2
        iLen2 = iLen1 + (iExtra Mod 2)
        StrJustifyCenter$ = String$(iLen1, " ") + sValue + String$(iLen2, " ")
    Else
        ' string is too long: truncate
        StrJustifyCenter$ = Left$(sValue, iWidth)
    End If
End Function ' StrJustifyCenter$

' /////////////////////////////////////////////////////////////////////////////
' Use to pretty print TRUE and FALSE values.

Function TrueFalse$ (myValue)
    If myValue = TRUE Then
        TrueFalse$ = "TRUE"
    Else
        TrueFalse$ = "FALSE"
    End If
End Function ' TrueFalse$

' /////////////////////////////////////////////////////////////////////////////

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' END GENERAL PURPOSE ROUTINES @GEN
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' BEGIN COLOR FUNCTIONS #COLOR
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Function cRed~& ()
    cRed = _RGB32(255, 0, 0)
End Function

Function cOrangeRed~& ()
    cOrangeRed = _RGB32(255, 69, 0)
End Function ' cOrangeRed~&

Function cDarkOrange~& ()
    cDarkOrange = _RGB32(255, 140, 0)
End Function ' cDarkOrange~&

Function cOrange~& ()
    cOrange = _RGB32(255, 165, 0)
End Function ' cOrange~&

Function cGold~& ()
    cGold = _RGB32(255, 215, 0)
End Function ' cGold~&

Function cYellow~& ()
    cYellow = _RGB32(255, 255, 0)
End Function ' cYellow~&

' LONG-HAIRED FRIENDS OF JESUS OR NOT,
' THIS IS NOT YELLOW ENOUGH (TOO CLOSE TO LIME)
' TO USE FOR OUR COMPLEX RAINBOW SEQUENCE:
Function cChartreuse~& ()
    cChartreuse = _RGB32(127, 255, 0)
End Function ' cChartreuse~&

' WE SUBSTITUTE THIS CSS3 COLOR FOR INBETWEEN LIME AND YELLOW:
Function cOliveDrab1~& ()
    cOliveDrab1 = _RGB32(192, 255, 62)
End Function ' cOliveDrab1~&

Function cLime~& ()
    cLime = _RGB32(0, 255, 0)
End Function ' cLime~&

Function cMediumSpringGreen~& ()
    cMediumSpringGreen = _RGB32(0, 250, 154)
End Function ' cMediumSpringGreen~&

' ADDED THIS FOR THE GAUGE COLOR:
Function cSpringGreen~& ()
    cSpringGreen = _RGB32(0, 255, 160)
End Function ' cSpringGreen~&

Function cCyan~& ()
    cCyan = _RGB32(0, 255, 255)
End Function ' cCyan~&

Function cDeepSkyBlue~& ()
    cDeepSkyBlue = _RGB32(0, 191, 255)
End Function ' cDeepSkyBlue~&

Function cDodgerBlue~& ()
    cDodgerBlue = _RGB32(30, 144, 255)
End Function ' cDodgerBlue~&

Function cSeaBlue~& ()
    cSeaBlue = _RGB32(0, 64, 255)
End Function ' cSeaBlue~&

Function cBlue~& ()
    cBlue = _RGB32(0, 0, 255)
End Function ' cBlue~&

Function cBluePurple~& ()
    cBluePurple = _RGB32(64, 0, 255)
End Function ' cBluePurple~&

Function cDeepPurple~& ()
    cDeepPurple = _RGB32(96, 0, 255)
End Function ' cDeepPurple~&

Function cPurple~& ()
    cPurple = _RGB32(128, 0, 255)
End Function ' cPurple~&

Function cPurpleRed~& ()
    cPurpleRed = _RGB32(128, 0, 192)
End Function ' cPurpleRed~&

Function cDarkRed~& ()
    cDarkRed = _RGB32(160, 0, 64)
End Function ' cDarkRed~&

Function cBrickRed~& ()
    cBrickRed = _RGB32(192, 0, 32)
End Function ' cBrickRed~&

Function cDarkGreen~& ()
    cDarkGreen = _RGB32(0, 100, 0)
End Function ' cDarkGreen~&

Function cGreen~& ()
    cGreen = _RGB32(0, 128, 0)
End Function ' cGreen~&

Function cOliveDrab~& ()
    cOliveDrab = _RGB32(107, 142, 35)
End Function ' cOliveDrab~&

Function cLightPink~& ()
    cLightPink = _RGB32(255, 182, 193)
End Function ' cLightPink~&

Function cHotPink~& ()
    cHotPink = _RGB32(255, 105, 180)
End Function ' cHotPink~&

Function cDeepPink~& ()
    cDeepPink = _RGB32(255, 20, 147)
End Function ' cDeepPink~&

Function cMagenta~& ()
    cMagenta = _RGB32(255, 0, 255)
End Function ' cMagenta~&

Function cBlack~& ()
    cBlack = _RGB32(0, 0, 0)
End Function ' cBlack~&

Function cDimGray~& ()
    cDimGray = _RGB32(105, 105, 105)
End Function ' cDimGray~&

Function cGray~& ()
    cGray = _RGB32(128, 128, 128)
End Function ' cGray~&

Function cDarkGray~& ()
    cDarkGray = _RGB32(169, 169, 169)
End Function ' cDarkGray~&

Function cSilver~& ()
    cSilver = _RGB32(192, 192, 192)
End Function ' cSilver~&

Function cLightGray~& ()
    cLightGray = _RGB32(211, 211, 211)
End Function ' cLightGray~&

Function cGainsboro~& ()
    cGainsboro = _RGB32(220, 220, 220)
End Function ' cGainsboro~&

Function cWhiteSmoke~& ()
    cWhiteSmoke = _RGB32(245, 245, 245)
End Function ' cWhiteSmoke~&

Function cWhite~& ()
    cWhite = _RGB32(255, 255, 255)
    'cWhite = _RGB32(254, 254, 254)
End Function ' cWhite~&

Function cDarkBrown~& ()
    cDarkBrown = _RGB32(128, 64, 0)
End Function ' cDarkBrown~&

Function cLightBrown~& ()
    cLightBrown = _RGB32(196, 96, 0)
End Function ' cLightBrown~&

Function cKhaki~& ()
    cKhaki = _RGB32(240, 230, 140)
End Function ' cKhaki~&

Function cEmpty~& ()
    'cEmpty~& = -1
    cEmpty = _RGB32(0, 0, 0, 0)
End Function ' cEmpty~&

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' END COLOR FUNCTIONS @COLOR
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' ################################################################################################################################################################
' BEGIN KEYBOARD CODE FUNCTIONS #KEYCODE
'
' ALL CODES ARE FOR _BUTTON, EXCEPT:
' * cF10 (_KEYDOWN)
' * cAltLeft (_KEYHIT)
' * cAltRight (_KEYHIT)
' * cPrintScreen (_KEYHIT) <- may slow down pc?
' * cPauseBreak (_KEYHIT) <- may not work?
' ################################################################################################################################################################

Function KeyCode_Escape% ()
    KeyCode_Escape% = 2
End Function

Function KeyCode_F1% ()
    KeyCode_F1% = 60
End Function

Function KeyCode_F2% ()
    KeyCode_F2% = 61
End Function

Function KeyCode_F3% ()
    KeyCode_F3% = 62
End Function

Function KeyCode_F4% ()
    KeyCode_F4% = 63
End Function

Function KeyCode_F5% ()
    KeyCode_F5% = 64
End Function

Function KeyCode_F6% ()
    KeyCode_F6% = 65
End Function

Function KeyCode_F7% ()
    KeyCode_F7% = 66
End Function

Function KeyCode_F8% ()
    KeyCode_F8% = 67
End Function

Function KeyCode_F9% ()
    KeyCode_F9% = 68
End Function

'_KEYDOWN CODE, NOT _BUTTON CODE
Function KeyCode_F10% ()
    KeyCode_F10% = 17408
End Function

Function KeyCode_F11% ()
    KeyCode_F11% = 88
End Function

Function KeyCode_F12% ()
    KeyCode_F12% = 89
End Function

'_KEYHIT CODE, NOT _BUTTON CODE
Function KeyCode_PrintScreen% ()
    KeyCode_PrintScreen% = -44
End Function

Function KeyCode_ScrollLock% ()
    KeyCode_ScrollLock% = 71
End Function

'_KEYHIT CODE, NOT _BUTTON CODE
Function KeyCode_PauseBreak% ()
    KeyCode_PauseBreak% = 31053
End Function

Function KeyCode_Tilde% ()
    KeyCode_Tilde% = 42
End Function

Function KeyCode_1% ()
    KeyCode_1% = 3
End Function

Function KeyCode_2% ()
    KeyCode_2% = 4
End Function

Function KeyCode_3% ()
    KeyCode_3% = 5
End Function

Function KeyCode_4% ()
    KeyCode_4% = 6
End Function

Function KeyCode_5% ()
    KeyCode_5% = 7
End Function

Function KeyCode_6% ()
    KeyCode_6% = 8
End Function

Function KeyCode_7% ()
    KeyCode_7% = 9
End Function

Function KeyCode_8% ()
    KeyCode_8% = 10
End Function

Function KeyCode_9% ()
    KeyCode_9% = 11
End Function

Function KeyCode_0% ()
    KeyCode_0% = 12
End Function

Function KeyCode_Minus% ()
    KeyCode_Minus% = 13
End Function

Function KeyCode_Equal% ()
    KeyCode_Equal% = 14
End Function

Function KeyCode_BkSp% ()
    KeyCode_BkSp% = 15
End Function

Function KeyCode_Ins% ()
    KeyCode_Ins% = 339
End Function

Function KeyCode_Home% ()
    KeyCode_Home% = 328
End Function

Function KeyCode_PgUp% ()
    KeyCode_PgUp% = 330
End Function

Function KeyCode_Del% ()
    KeyCode_Del% = 340
End Function

Function KeyCode_End% ()
    KeyCode_End% = 336
End Function

Function KeyCode_PgDn% ()
    KeyCode_PgDn% = 338
End Function

Function KeyCode_NumLock% ()
    KeyCode_NumLock% = 326
End Function

Function KeyCode_KeypadSlash% ()
    KeyCode_KeypadSlash% = 310
End Function

Function KeyCode_KeypadMultiply% ()
    KeyCode_KeypadMultiply% = 56
End Function

Function KeyCode_KeypadMinus% ()
    KeyCode_KeypadMinus% = 75
End Function

Function KeyCode_Keypad7Home% ()
    KeyCode_Keypad7Home% = 72
End Function

Function KeyCode_Keypad8Up% ()
    KeyCode_Keypad8Up% = 73
End Function

Function KeyCode_Keypad9PgUp% ()
    KeyCode_Keypad9PgUp% = 74
End Function

Function KeyCode_KeypadPlus% ()
    KeyCode_KeypadPlus% = 79
End Function

Function KeyCode_Keypad4Left% ()
    KeyCode_Keypad4Left% = 76
End Function

Function KeyCode_Keypad5% ()
    KeyCode_Keypad5% = 77
End Function

Function KeyCode_Keypad6Right% ()
    KeyCode_Keypad6Right% = 78
End Function

Function KeyCode_Keypad1End% ()
    KeyCode_Keypad1End% = 80
End Function

Function KeyCode_Keypad2Down% ()
    KeyCode_Keypad2Down% = 81
End Function

Function KeyCode_Keypad3PgDn% ()
    KeyCode_Keypad3PgDn% = 82
End Function

Function KeyCode_KeypadEnter% ()
    KeyCode_KeypadEnter% = 285
End Function

Function KeyCode_Keypad0Ins% ()
    KeyCode_Keypad0Ins% = 83
End Function

Function KeyCode_KeypadPeriodDel% ()
    KeyCode_KeypadPeriodDel% = 84
End Function

Function KeyCode_Tab% ()
    KeyCode_Tab% = 16
End Function

Function KeyCode_Q% ()
    KeyCode_Q% = 17
End Function

Function KeyCode_W% ()
    KeyCode_W% = 18
End Function

Function KeyCode_E% ()
    KeyCode_E% = 19
End Function

Function KeyCode_R% ()
    KeyCode_R% = 20
End Function

Function KeyCode_T% ()
    KeyCode_T% = 21
End Function

Function KeyCode_Y% ()
    KeyCode_Y% = 22
End Function

Function KeyCode_U% ()
    KeyCode_U% = 23
End Function

Function KeyCode_I% ()
    KeyCode_I% = 24
End Function

Function KeyCode_O% ()
    KeyCode_O% = 25
End Function

Function KeyCode_P% ()
    KeyCode_P% = 26
End Function

Function KeyCode_BracketLeft% ()
    KeyCode_BracketLeft% = 27
End Function

Function KeyCode_BracketRight% ()
    KeyCode_BracketRight% = 28
End Function

Function KeyCode_Backslash% ()
    KeyCode_Backslash% = 44
End Function

Function KeyCode_CapsLock% ()
    KeyCode_CapsLock% = 59
End Function

Function KeyCode_A% ()
    KeyCode_A% = 31
End Function

Function KeyCode_S% ()
    KeyCode_S% = 32
End Function

Function KeyCode_D% ()
    KeyCode_D% = 33
End Function

Function KeyCode_F% ()
    KeyCode_F% = 34
End Function

Function KeyCode_G% ()
    KeyCode_G% = 35
End Function

Function KeyCode_H% ()
    KeyCode_H% = 36
End Function

Function KeyCode_J% ()
    KeyCode_J% = 37
End Function

Function KeyCode_K% ()
    KeyCode_K% = 38
End Function

Function KeyCode_L% ()
    KeyCode_L% = 39
End Function

Function KeyCode_Semicolon% ()
    KeyCode_Semicolon% = 40
End Function

Function KeyCode_Apostrophe% ()
    KeyCode_Apostrophe% = 41
End Function

Function KeyCode_Enter% ()
    KeyCode_Enter% = 29
End Function

Function KeyCode_ShiftLeft% ()
    KeyCode_ShiftLeft% = 43
End Function

Function KeyCode_Z% ()
    KeyCode_Z% = 45
End Function

Function KeyCode_X% ()
    KeyCode_X% = 46
End Function

Function KeyCode_C% ()
    KeyCode_C% = 47
End Function

Function KeyCode_V% ()
    KeyCode_V% = 48
End Function

Function KeyCode_B% ()
    KeyCode_B% = 49
End Function

Function KeyCode_N% ()
    KeyCode_N% = 50
End Function

Function KeyCode_M% ()
    KeyCode_M% = 51
End Function

Function KeyCode_Comma% ()
    KeyCode_Comma% = 52
End Function

Function KeyCode_Period% ()
    KeyCode_Period% = 53
End Function

Function KeyCode_Slash% ()
    KeyCode_Slash% = 54
End Function

Function KeyCode_ShiftRight% ()
    KeyCode_ShiftRight% = 55
End Function

Function KeyCode_Up% ()
    KeyCode_Up% = 329
End Function

Function KeyCode_Left% ()
    KeyCode_Left% = 332
End Function

Function KeyCode_Down% ()
    KeyCode_Down% = 337
End Function

Function KeyCode_Right% ()
    KeyCode_Right% = 334
End Function

Function KeyCode_CtrlLeft% ()
    KeyCode_CtrlLeft% = 30
End Function

Function KeyCode_WinLeft% ()
    KeyCode_WinLeft% = 348
End Function

' _KEYHIT CODE NOT _BUTTON CODE
Function KeyCode_AltLeft% ()
    KeyCode_AltLeft% = -30764
End Function

Function KeyCode_Spacebar% ()
    KeyCode_Spacebar% = 58
End Function

' _KEYHIT CODE NOT _BUTTON CODE
Function KeyCode_AltRight% ()
    KeyCode_AltRight% = -30765
End Function

Function KeyCode_WinRight% ()
    KeyCode_WinRight% = 349
End Function

Function KeyCode_Menu% ()
    KeyCode_Menu% = 350
End Function

Function KeyCode_CtrlRight% ()
    KeyCode_CtrlRight% = 286
End Function

' ################################################################################################################################################################
' END KEYBOARD CODE FUNCTIONS @KEYCODE
' ################################################################################################################################################################

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' BEGIN DEBUGGING ROUTINES #DEBUG
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Sub DebugPrint (MyString As String)
    If m_bDebug = TRUE Then
        '_Echo MyString

        ReDim arrLines(-1) As String
        Dim iLoop As Integer
        split MyString, Chr$(13), arrLines()
        For iLoop = LBound(arrLines) To UBound(arrLines)
            _Echo arrLines(iLoop)
        Next iLoop
    End If
End Sub ' DebugPrint

'' /////////////////////////////////////////////////////////////////////////////
'
'Sub DebugPause (sPrompt As String, iRow As Integer, iColumn As Integer, fgColor As _Unsigned Long, bgColor As _Unsigned Long)
'    Color fgColor, bgColor
'
'    PrintString iRow, iColumn, String$(128, " ")
'
'    PrintString iRow, iColumn, sPrompt
'    Sleep
'    '_KEYCLEAR: _DELAY 1
'    'DO
'    'LOOP UNTIL _KEYDOWN(13) ' leave loop when ENTER key pressed
'    '_KEYCLEAR: _DELAY 1
'End Sub ' DebugPause
'
'' /////////////////////////////////////////////////////////////////////////////
'
'Sub DebugOut (sPrompt As String, iRow As Integer, iColumn As Integer, fgColor As _Unsigned Long, bgColor As _Unsigned Long)
'    Color fgColor, bgColor
'    PrintString iRow, iColumn, String$(128, " ")
'    PrintString iRow, iColumn, sPrompt
'End Sub ' DebugOut

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' END DEBUGGING ROUTINES @DEBUG
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' ################################################################################################################################################################
' BEGIN REFERENCE #REF
' ################################################################################################################################################################

' =============================================================================
' SOME USEFUL STUFF FOR REFERENCE:

' Type Name               Type suffix symbol   Minimum value                  Maximum value                Size in Bytes
' ---------------------   ------------------   ----------------------------   --------------------------   -------------
' _BIT                    `                    -1                             0                            1/8
' _BIT * n                `n                   -128                           127                          n/8
' _UNSIGNED _BIT          ~`                   0                              1                            1/8
' _BYTE                   %%                   -128                           127                          1
' _UNSIGNED _BYTE         ~%%                  0                              255                          1
' INTEGER                 %                    -32,768                        32,767                       2
' _UNSIGNED INTEGER       ~%                   0                              65,535                       2
' LONG                    &                    -2,147,483,648                 2,147,483,647                4
' _UNSIGNED LONG          ~&                   0                              4,294,967,295                4
' _INTEGER64              &&                   -9,223,372,036,854,775,808     9,223,372,036,854,775,807    8
' _UNSIGNED _INTEGER64    ~&&                  0                              18,446,744,073,709,551,615   8
' SINGLE                  ! or none            -2.802597E-45                  +3.402823E+38                4
' DOUBLE                  #                    -4.490656458412465E-324        +1.797693134862310E+308      8
' _FLOAT                  ##                   -1.18E-4932                    +1.18E+4932                  32(10 used)
' _OFFSET                 %&                   -9,223,372,036,854,775,808     9,223,372,036,854,775,807    Use LEN
' _UNSIGNED _OFFSET       ~%&                  0                              18,446,744,073,709,551,615   Use LEN
' _MEM                    none                 combined memory variable type  N/A                          Use LEN

' div: int1% = num1% \ den1%
' mod: rem1% = num1% MOD den1%

' ################################################################################################################################################################
' END REFERENCE @REF
' ################################################################################################################################################################

'#END

Print this item

Wink My first qb64 game
Posted by: Gadgetjack - 11-03-2022, 04:58 PM - Forum: Programs - Replies (7)

[Image: screenshot.jpg]

I have just started again with qb64 after retiring , never had a lot of time for it before, and tried my hand at creating a game. Don't laugh , it is my first. I "borrowed" a lot of parts from the tutorial and sprite library to get it going. All the files are in the zip. Do with it what you will.
If you add some features to it , let me know so I can learn from your coding skills. I will be in and reading a lot more now that I have time.
Jack
.zip   MarioFood.zip (Size: 3.04 MB / Downloads: 42)

Print this item

  Win32 API SetClipboardData
Posted by: Pete - 11-03-2022, 12:00 PM - Forum: Help Me! - Replies (4)

@Spriggsy

I tried to add a SetClipboardData to your WinAPI routine below, but no luck. No luck as in my addition of SetClipboardData caused the program to crash before the console even opened. I posted your routine below. Could it be modified so instead of just reading the present clipboard contents, it gets the highlighted text from the active window and adds that highlighted text to the clipboard? If that would be a PITA to code, just let me know and don't go to the trouble. What I'm looking for is an API alternative to using _SCREENPRINT CHR$(3) because in our IDE, if applied from another program, it doesn't copy the QB64 text, it just removes the highlighted text and leaves  lowercase c in its place.

Code: (Select All)
' Display Clipboard Contents by Spriggsy.
OPTION _EXPLICIT
$CONSOLE:ONLY
_DEST _CONSOLE
'CONSTANTS
CONST CF_TEXT = 1
CONST CF_BITMAP = 2
CONST CF_METAFILEPICT = 3
CONST CF_SYLK = 4
CONST CF_DIF = 5
CONST CF_TIFF = 6
CONST CF_OEMTEXT = 7
CONST CF_DIB = 8
CONST CF_PALETTE = 9
CONST CF_PENDATA = 10
CONST CF_RIFF = 11
CONST CF_WAVE = 12
CONST CF_UNICODETEXT = 13
CONST CF_ENHMETAFILE = 14
CONST CF_HDROP = 15
CONST CF_LOCALE = 16
CONST CF_DIBV5 = 17
CONST CF_MAX = 18

DECLARE DYNAMIC LIBRARY "User32"
    FUNCTION OpenClipboard%% (BYVAL hWndNewOwner AS LONG)
    'FUNCTION CountClipboardFormats% ()
    FUNCTION GetClipboardData& (BYVAL uFormat AS _UNSIGNED INTEGER)
    FUNCTION CloseClipboard%% ()
END DECLARE

DECLARE DYNAMIC LIBRARY "Kernel32"
    FUNCTION GlobalLock%& (BYVAL hMem AS LONG)
    FUNCTION GlobalUnlock%% (BYVAL hMem AS LONG)
END DECLARE

DECLARE CUSTOMTYPE LIBRARY "peekpoke"
    FUNCTION peekb~%% (BYVAL p AS _UNSIGNED _OFFSET) 'Byte
END DECLARE

PRINT Clipboard$
SLEEP

FUNCTION Clipboard$
    DIM a AS LONG
    a = OpenClipboard(0)
    a = GetClipboardData(CF_TEXT)
    DIM b AS _UNSIGNED _OFFSET
    b = GlobalLock(a)
    DIM x AS _UNSIGNED INTEGER
    DIM clip AS STRING
    DO
        clip = clip + CHR$(peekb(b + x))
        x = x + 1
    LOOP UNTIL CHR$(peekb(b + x)) = CHR$(0)
    DIM closeclip AS _BYTE
    closeclip = GlobalUnlock(a)
    closeclip = CloseClipboard
    Clipboard = clip
END FUNCTION

Pete

Print this item

  A game from PhilOfPerth
Posted by: PhilOfPerth - 11-03-2022, 01:52 AM - Forum: Works in Progress - Replies (8)

No matter the quorum, I'll be first on this Forum!  Big Grin

Here's a game I've nearly finished writing... needs to be tidied up yet though.

Code: (Select All)
Screen 9
_FullScreen
Clear
DefInt A-Z
Common Shared try$, fail, tries, prev$, tryvert, targets(), target, firstwords$(), first$, lastwords$(), last$, pairnumber$, pairnumber, names$(), name$, ok$, fail$, temp$
Common Shared added$, removed$, ln$, train$()

maxtries = 20: minsize = 2: ok$ = "o3l32cego4c": fail$ = "o2l16co1gec"
Dim firstwords$(20), lastwords$(20), targets(20), names$(20), train$(20)
Randomize Timer

Data "BIG","SMALL","LION","TIGER","CAR","TRUCK","BLACK","WHITE","WEED","FLOWER","BEDROOM","KITCHEN","COPPER","BRASS","DESERT","OASIS","MILK","HONEY","HORSE","SHEEP"
Data "BADGE","MEDAL","MARRY","DIVORCE","SHED","HOUSE","WAR","PEACE","SUIT","DRESS","BOX","CARTON","ROAD","STREET","DUNCE","GENIUS","CUP","PLATE","STEAK","EGGS"

'Data "ORB","SCEPTRE","TOWN","VILLAGE","BURGER","CHIPS","YOUTH","MAIDEN","OLD","NEW","FAKE","GENUINE","TEA","COFFEE","DRESS","SKIRT","PLANTS","WEEDS","PENCIL","CRAYON"
'Data "GLASS","BEAKER","GUITAR","PIANO","SLATE","STONE","CORD","ROPE","JUNGLE","DESERT","PANTRY","CUPBOARD","BROOM","SHOVEL","FOOD","DRINK","ORANGE","LEMON","SINNER","SAINT"


AlchemyDescription:
Print
Color 14
Print Tab(36); "ALCHEMY": Color 15
Print
Print " Alchemy (al/ke/mi) can be defined as the process of changing something into"
Print " something different in a mystical way, such as changing ";: Color 14: Print "STONE";: Color 15
Print " into ";: Color 14: Print "GOLD.": Color 15
Print
Print " This game calls upon your skills in this art, to change a word into a"
Print " totally different one, with the least number of changes."
Print
Print " In the usual word-swap game, you repeatedly change one letter of a word for a"
Print " different one, creating a new word, until the target word is produced."
Print
Print " But in Alchemy, you have another tool available to you for the transformation."
Print " You can also ";: Color 14: Print "add";: Color 15: Print " or ";: Color 14: Print "remove";: Color 15: Print " a letter, before re-arranging them, so the word may"
Print " change in length several times as you progress."
Print
Print " As an example, we can change STONE into GOLD with 4 changes:"
Color 14: Print Tab(23); "STONE - TONE - GONE - LONG - GOLD": Color 15
Print
Print " If the wordslists directory is present, each word entered is checked against"
Print " these. If not, they are assumed to be legitimate words."
Print " The wordlist files are the Complete Collins Scrabble Words (2019)."
Print: Color 14
Print Tab(29); "Press a key to continue"
While InKey$ = "": Wend
Play ok$
LoadPairs

Choice: '                                                                                     invites replacing best scores in file with defaults
Color 14
Locate 23, 17
Print "Would you like to delete all previous Best Scores (y/n)";
Sleep
Color 15: y$ = UCase$(InKey$)
If y$ = "Y" Then
    Refresh
    Play ok$
End If

SetPair: '                                                                                     Select pair of words
LoadPairs
Color 14: Print Tab(22); "Which pair would you like, from A to T";
getpair:
pair$ = UCase$(InKey$)
If pair$ = "" Or pair$ = Chr$(13) Then GoTo getpair
If pair$ = Chr$(27) Then Stop
If pair$ < "A" Or pair$ > "T" Then pair$ = Chr$(65 + Int(Rnd * 20))
pairnumber = Asc(pair$) - 64

StartGame:
Cls
remain = 21: tries = 0: fail = 0 '                                                             start each game with 21 tries remaining
first$ = firstwords$(pairnumber): last$ = lastwords$(pairnumber)
train$(pairnumber) = first$
target = targets(pairnumber): name$ = names$(pairnumber) '                                      get  selected pair details
prev$ = first$ '                                                                                pretend the first was a previous try
Color 14
Locate 1, 39 - Int(Len(first$) / 2): Print first$; Tab(52); "Record:"; target '                 display the first word in yellow on row 2
Color 15
For a = 2 To maxtries + 1: Locate a, 35
Print String$(9, "."): Next '                                                                   show 9 dots for each try (rows 2 to 21)
Color 14
Locate 22, 39 - Int(Len(last$) / 2): Print last$; '                                             display the last word in yellow on row 23
tryvert = 2 '                                                                                   row 3 will take the first try

InviteTry:
If tries = maxtries Then
    Play fail$
    WIPE "23": Color 3:
    Locate 23, 21: Print "You've Used up all of your tries, sorry!"
    WIPE "24"
    Color 15
    Sleep 3
    GoTo StartGame '                                                                             ran out of tries, restart the same pair
Else
    Locate tryvert, 35: Print String$(9, "."); Tab(46); Space$(30)
    WIPE "23": Color 14 '                                                                        refresh remaining tries advice
    Locate 23, 27
    Print "You have"; 20 - tries; "tries remaining"
    Locate tryvert, 3 '                                                                          display invite at tab 10 of current try-line
    Print "Your word (q to quit)";
End If

DealWithTry:
Locate tryvert, 25
Input try$ '                                                                                     show ? outside try-line and set try to first dot
Color 15
try$ = UCase$(try$)
If try$ = "Q" Then Stop
If try$ < "A" Or try$ > "Z" Then Play fail$: GoTo SetPair
tries = tries + 1
Locate tryvert, 35: Print Space$(12)
Locate tryvert, 39 - Int(Len(try$) / 2): Print try$
CheckWord '                                                                                       Call Sub to Check the Player's Word

DealWithCheck:
Locate tryvert, 1: Print Space$(35)
If fail = 1 Then
    Locate tryvert, 35: Print "         "
    Color 3
    Locate tryvert, 39 - Len(try$) / 2
    Print try$
    Color 15
    tryvert = tryvert + 1
    GoTo InviteTry
Else
    If try$ = last$ Then
        Finished
        GoTo SetPair
    Else
        Locate 23, 30
        Print Space$(50)
        tryvert = tryvert + 1
        GoTo InviteTry
    End If
End If

Sub Refresh
    Restore
    target = 21: name$ = "Unsolved"
    Open "alchpairs" For Output As #1
    For a = 1 To 20
        Read first$, last$
        Write #1, first$, last$, target, name$, ""
        Print first$; " "; last$; target; name$
    Next
    Close
    Cls
End Sub

Sub WIPE (ln$) '                                                                                  call with ln$ string of 2-digit line numbers only  eg "012223"  for lines 1, 22 and 23
    For a = 1 To Len(ln$) - 1 Step 2
        Locate Val(Mid$(ln$, a, 2)): Print Space$(80);
    Next
End Sub

Sub LoadPairs
    Restore
    Cls
    Color 14: Print Tab(37); "Word Pairs"
    Print Tab(20); "Pair"; Tab(30); "From"; Tab(41); "To"; Tab(50); "Best"; Tab(62); "By"
    Color 15
    If _FileExists("alchpairs") Then
        Open "alchpairs" For Input As #1
        For a = 1 To 20
            Input #1, firstwords$(a), lastwords$(a), targets(a), names$(a), train$(a) '                      loads word-pairs from "alchpairs" file
            Color 14: Print Tab(20); Chr$(a + 64);: Color 15: Print Tab(30); firstwords$(a); Tab(40); lastwords$(a); Tab(50); targets(a); Tab(60); names$(a)
        Next
        Close #1
    Else Refresh
    End If
End Sub

Sub CheckWord
    added = 0: added$ = "": removed = 0: removed$ = "": fail = 0 '                                 initialise added, removed and fail flag
    Locate tryvert, 48: Print Space$(32)
    Locate tryvert, 48
    CountAdded:
    temp$ = prev$ '                                                                                 use temp$ as sacrificial to keep prev$ intact while checking for added
    For a = 1 To Len(try$) '                                                                        for each letter in try$...
        l$ = Mid$(try$, a, 1) '                                                                     take a letter l$ of temp$
        po = InStr(temp$, l$) '                                                                     find its position po in temp$ (if any)
        If po < 1 Then '                                                                            if not found...
            added = added + 1
            added$ = added$ + l$ '                                                                   count it and add to added$
        Else
            Mid$(temp$, po, 1) = " "
        End If
    Next

    CountRemoved:
    temp$ = try$ '                                                                                     use temp$ as sacrificial to keep prev$ intact while checking for added
    For a = 1 To Len(prev$) '                                                                          for each letter in try$...
        l$ = Mid$(prev$, a, 1) '                                                                       take a letter l$ of temp$
        po = InStr(temp$, l$) '                                                                        find its position po in temp$ (if any)
        If po < 1 Then '                                                                               if not found...
            removed = removed + 1
            removed$ = removed$ + l$ '                                                                 add it to added$
        Else
            Mid$(temp$, po, 1) = " "
        End If
    Next
    If added > 1 Then Color 3 Else Color 15
    Print "Added "; added$;
    If removed > 1 Then Color 3 Else Color 15
    Print Tab(60); "Removed "; removed$ '                                                               show letters that have been added or removed, colour cyan if too many

    DictionaryCheck:
    If Not _DirExists("wordlists") Then isaword = 1: GoTo checksfinished
    WIPE "23"
    filename$ = "wordlists/" + Left$(try$, 1) '                                                        select dictionary file of first letter of try-word
    Open filename$ For Input As #1
    getaword:
    isaword = 0
    While Not EOF(1)
        Input #1, dictword$ '                                                                          read each word from dictionary
        If try$ = dictword$ Then isaword = 1: Exit While '                                             if word is found, don't look any further
    Wend
    Close
    checksfinished:
    Locate 23, 1
    If added > 1 Or removed > 1 Or isaword = 0 Then '                                                  if more than one letter added or removed, or word not found, set fail flag
        Play fail$
        Color 3 '                                                                                      colour of try changed to cyan if word failed
        Print Tab(35); "Word failed";
        Color 15
        fail = 1
    Else
        Play ok$
        Print Tab(37); "Word ok"; '                                                                     otherwise, declare word as ok and make this the new prev$
        prev$ = try$
        train$(pairnumber) = train$(pairnumber) + "-" + try$
    End If
    Sleep 1
    WIPE "23"
End Sub

Sub Finished
    Play ok$: Play ok$
    Locate tryvert, 35: Print Space$(12)
    Locate tryvert, 39 - Len(try$) / 2: Print try$
    WIPE "2223"
    Locate 22, 21: Color 14: Print "You did it in"; tries; "changes.  Target was"; targets(pairnumber)
    Sleep 2
    If tries >= targets(pairnumber) Then '                                                              if target is not beaten,
        Exit Sub '                                                                                      go back for next game
    Else
        targets(pairnumber) = tries '                                                                   change the target for that pair to the new best score
        Cls
        Locate 10, 4
        Input "Enter a name for the Best Scores list (or <ENTER> for anonymous)"; winname$ '            get the player's name
        If Len(winname$) < 2 Then winname$ = "ANONYMOUS" '                                              if <ENTER> (or only one character) is given, name is Anonymous
        names$(pairnumber) = UCase$(winname$) '                                                         change the name for that pair to the new name
        Open "alchpairs" For Output As #1
        For a = 1 To 20
            Write #1, firstwords$(a), lastwords$(a), targets(a), names$(a), train$(a) '                            re-write the alchpairs file with the new details
        Next
        Close
    End If
    Cls
    Locate 10, 40 - Len(train$(pairnumber)) / 2: Print train$(pairnumber)
    Print: Print Tab(36); "Press a key"
    Sleep
End Sub



Attached Files
.zip   wordlists.zip (Size: 713.57 KB / Downloads: 24)
Print this item

Brick Libraries Collection
Posted by: RhoSigma - 11-02-2022, 11:11 PM - Forum: RhoSigma - No Replies

A collection of useful libraries for QB64


This collection of libraries was developed and has grown over the period of approximately one decade. Most of the libraries were created out of my own need for a particular purpose, but as many of it are of common use, I've decided to put this collection together for everybody.

These libraries work with every QB64 version since v0.954, so if you're one of these normal people who not update to the latest build every week, that's not a problem. Just use whatever QB64 version you've installed, as long it's greater or equal to the 0.954 version. This also includes all Phoenix Edition versions.

The main parts are:
  • Imageprocessing effects and filters
  • Polygon drawing and analysis (see pictures below)
  • The Simplebuffer System
  • LZW packing and unpacking
  • Base64 encoding and decoding
  • MD5 and SHA2 hash generation
  • DES-56 encryption and decryption
  • several wrappers to standard library functions

.7z   QB64Library.7z (Size: 355.83 KB / Downloads: 90) --- (Dec/22)(Libraries Collection)

Make sure to move the extracted QB64Library folder with its entire contents into your QB64 installation folder. You find an overview of all libraries in the QB64Library-Info.html file.



Attached Files Thumbnail(s)
           
Print this item

  RoCoLoco Revisited - Math puzzle.
Posted by: Dav - 11-02-2022, 02:12 AM - Forum: Programs - Replies (4)

This is an update to a math adding puzzle game I posted a few years ago.  The previous version was limited and used dozens of image files for numbers.   I added a new board display method, it should look the same size on every desktop.  This version has more grid sizes, and some sound effects, and a simple Help screen.

Basically you click to remove/add numbers in the grid so that the ones left on add up to the number shown on the edges.  The rows and columns numbers must total up to the number on the edge.  Make all the edge numbers highlighted to solve the puzzle and advance to a bigger grid size.  You can change grid sizes by pressing +/- keys.  

It's nothing great, and probably could be done in half the amount of code, I just wanted to work on something today and landed on this for some reason.  

- Dav

Code: (Select All)
'============
'RoCoLoco.bas v2.0
'============
'Row & Column number adding puzzle game.
'Based on popular math game found online.
'Coded by Dav for QB64-PE, Nov/2022

'New for v2:  * Playing board now adapts to users desktop size.
'               It should displays the same on every screen.
'               (Screen size is not hardcoded to specific size)
'             * No longer uses external .jpg images for numbers.
'             * Change grid size using the +/- keys.
'             * Added HELP screen (not much) - press H for it.
'             * Added some cheap sound effects.
'
'~~~~~~~~~~~
'HOW TO PLAY:

'Click numbers inside the grid to turn them on/off.
'Each row/column of red number totals must add up to the number
'on the edge of the grid. When they do, the edge numbers will
'turn on too. You must tun on all the edge numbers to solve it.
'
'~~~~~~~~
'CONTROLS:
'
'You can change the grid size anytime by using +/- keys.
'H = Show a simple help screen
'SPACE = Generate a new puzzle grid
'ESC = Quits game

'============================================================

REDIM SHARED grid, tiles

'defalts....
grid = 5 '3x3 grid size (3 for inside tiles, 2 for edges  = 5)
gridmax = 15

'===
top:
'===

_TITLE "RoCoLoco - Working..."

tiles = grid * grid '  total number of tiles on board

SCREEN _NEWIMAGE(_DESKTOPHEIGHT * .80, _DESKTOPHEIGHT * .80, 32)

tilesize = (_DESKTOPHEIGHT * .80) / grid

CLS , _RGB(77, 77, 77)

REDIM SHARED tilev(tiles) 'make array for all titles values
REDIM SHARED tilef(tiles) 'make array for flag if tile is on/off
REDIM SHARED tilex(tiles), tiley(tiles) 'x/y positions for tiles

RANDOMIZE TIMER 'set random seed

'init x/y values for drawing tiles
bc = 1
FOR x = 1 TO grid
    FOR y = 1 TO grid
        tilex(bc) = (x * tilesize) - tilesize: tiley(bc) = (y * tilesize) - tilesize
        tilev(bc) = INT(RND * 9 + 1) '<<< for testing only, give all some data
        bc = bc + 1
    NEXT
NEXT

SCRAMTIME = TIMER

'==========
regenerate:
'==========

SOUND 500 + RND * 1000, 1

'generate random tile value and on/off settings
T = grid + 2
FOR y = 2 TO grid - 1
    regeneratex:
    makesurex = 0: thold = T
    FOR x = 2 TO grid - 1
        tilev(thold) = INT(RND * 5 + 1) 'make random number, from 1-5
        IF INT(RND * grid - (INT(RND * 1.5))) = 0 THEN
            tilef(thold) = 0 'randomly turn tile on/off
            makesurex = 1 'make sure at least one out on this column
        ELSE
            tilef(thold) = 1
        END IF
        'show something while computing...
        LINE (tilex(thold), tiley(thold))-(tilex(thold) + tilesize, tiley(thold) + tilesize), _RGB(255, 100, 100), BF
        PPRINT tilex(thold) + (tilesize / 1.8), tiley(thold) + (tilesize / 4.5), tilesize / 2, _RGB(255, 255, 255), 0, LTRIM$(STR$(tilev(thold)))
        thold = thold + 1
    NEXT
    'if row didnt have one turned off, do this column over...
    IF makesurex = 0 THEN GOTO regeneratex
    T = thold + 2
    _LIMIT 10 * grid 'slow down, let's see it.
NEXT

'now check rows left to right for on/off, if none off, regenerate
FOR x = 0 TO grid - 3
    makesurex = 0
    FOR y = grid + 2 TO (grid * grid) - grid - 2 STEP grid
        IF tilef(y + x) = 0 THEN makesurex = 1
    NEXT
    IF makesurex = 0 THEN GOTO regenerate
NEXT

IF TIMER < (SCRAMTIME + 1) THEN GOTO regenerate

'compute left/right edges totals
FOR y = 0 TO grid - 3
    total = 0
    FOR x = grid + 2 TO (grid * grid) - grid - 2 STEP grid
        IF tilef(y + x) = 1 THEN
            total = total + tilev(y + x)
        END IF
    NEXT
    tilev(y + x) = total 'set total data to tile
NEXT

'compute top/bottom edges
FOR x = grid + 2 TO (grid * grid) - grid - 2 STEP grid
    total = 0
    FOR y = 0 TO grid - 3
        IF tilef(y + x) = 1 THEN
            total = total + tilev(y + x)
        END IF
    NEXT
    tilev(y + x) = total
NEXT

'Lastly, mark all inside grid tiles as on, hiding generated solution.
FOR T = grid + 2 TO (grid * grid) - grid - 1
    'skip the outside rows
    FOR tt = 1 TO grid * grid STEP grid
        IF T = tt THEN GOTO skipmark
        IF T = tt + (grid - 1) THEN GOTO skipmark
    NEXT
    tilef(T) = 1
    skipmark:
NEXT

GOSUB RedrawBoard

tit$ = "RoCoLoco" + STR$(grid - 2) + "x" + LTRIM$(STR$(grid - 2)) + " | H = Help"
_TITLE tit$
_ICON _DISPLAY

DO
    'wait until mouse button up to continue
    WHILE _MOUSEBUTTON(1) <> 0: n = _MOUSEINPUT: WEND

    trap = _MOUSEINPUT
    IF _MOUSEBUTTON(1) THEN
        mx = _MOUSEX: my = _MOUSEY
        'cycle through tiles
        FOR T = grid + 2 TO (grid * grid) - grid - 1
            'skip the outside rows
            FOR tt = 1 TO grid * grid STEP grid
                IF T = tt THEN GOTO skip
                IF T = tt + (grid - 1) THEN GOTO skip
            NEXT

            tx = tilex(T): tx2 = tilex(T) + tilesize
            ty = tiley(T): ty2 = tiley(T) + tilesize

            IF mx >= tx AND mx <= tx2 THEN
                IF my >= ty AND my <= ty2 THEN

                    IF tilef(T) = 0 THEN
                        tilef(T) = 1: 'mark it on
                        SOUND 2500, .1
                    ELSE
                        tilef(T) = 0: 'mark it off
                        SOUND 2000, .1
                    END IF

                    GOSUB RedrawBoard

                    'check for win

                END IF
            END IF
            skip:
        NEXT

    END IF

    k$ = UCASE$(INKEY$)
    IF k$ <> "" THEN

        'ESC key quits
        IF k$ = CHR$(27) THEN SYSTEM

        'space key generates new board
        IF k$ = " " THEN GOTO top

        IF k$ = "+" THEN
            IF grid < 15 THEN grid = grid + 1: GOTO top
        END IF

        IF k$ = "-" THEN
            IF grid > 5 THEN grid = grid - 1: GOTO top
        END IF

        IF k$ = "H" THEN
            back& = _COPYIMAGE(_DISPLAY)
            CLS , _RGB(77, 77, 77)
            ps = (_DESKTOPHEIGHT * .80) / 5
            PPRINT ps / 2 + 2, ps / 3 + 2, ps / 3, _RGB(255, 255, 255), 0, "ROCOLOCO HELP"
            PPRINT ps / 2, ps / 3, ps / 3, _RGB(255, 100, 100), 0, "ROCOLOCO HELP"
            PPRINT ps / 2, (ps / 3) * 3, ps / 6, _RGB(196, 196, 196), 0, "Click on the numbers to"
            PPRINT ps / 2, (ps / 3) * 4, ps / 6, _RGB(196, 196, 196), 0, "turn them On or Off."
            PPRINT ps / 2, (ps / 3) * 5, ps / 6, _RGB(196, 196, 196), 0, "Rows And Columns must"
            PPRINT ps / 2, (ps / 3) * 6, ps / 6, _RGB(196, 196, 196), 0, "add up to the number on"
            PPRINT ps / 2, (ps / 3) * 7, ps / 6, _RGB(196, 196, 196), 0, "the edges. When correct,"
            PPRINT ps / 2, (ps / 3) * 8, ps / 6, _RGB(196, 196, 196), 0, "edges will turn white."
            PPRINT ps / 2, (ps / 3) * 9, ps / 6, _RGB(196, 196, 196), 0, "Make all edges white to"
            PPRINT ps / 2, (ps / 3) * 10, ps / 6, _RGB(196, 196, 196), 0, "solve the math puzzle."
            PPRINT ps / 2, (ps / 3) * 11, ps / 6, _RGB(196, 196, 196), 0, "Use +/- keys to change"
            PPRINT ps / 2, (ps / 3) * 12, ps / 6, _RGB(196, 196, 196), 0, "the size of the grid."
            PPRINT ps / 2, (ps / 3) * 14, ps / 6, _RGB(255, 255, 255), 0, "   - PRESS ANY KEY - "
            A$ = INPUT$(1)
            _PUTIMAGE (0, 0), back&
            _FREEIMAGE back&
        END IF

    END IF

LOOP

END

'========================================================================================
RedrawBoard:
'===========

win = 1

'Draw inside grid numbers first
FOR d = grid + 2 TO (grid * grid) - grid - 1
    'skiping the outside rows
    FOR dd = 1 TO grid * grid STEP grid
        IF d = dd THEN GOTO skipit
        IF d = dd + (grid - 1) THEN GOTO skipit
    NEXT
    IF tilef(d) = 1 THEN
        'on
        LINE (tilex(d), tiley(d))-(tilex(d) + tilesize, tiley(d) + tilesize), _RGB(255, 100, 100), BF
        PPRINT tilex(d) + (tilesize / 1.8), tiley(d) + (tilesize / 4.5), tilesize / 2, _RGB(255, 255, 255), 0, LTRIM$(STR$(tilev(d)))
    ELSE
        'off
        LINE (tilex(d), tiley(d))-(tilex(d) + tilesize, tiley(d) + tilesize), _RGB(77, 77, 77), BF
        PPRINT tilex(d) + (tilesize / 1.8), tiley(d) + (tilesize / 4.5), tilesize / 2, _RGB(96, 96, 96), 0, LTRIM$(STR$(tilev(d)))
    END IF
    skipit:
NEXT

'compute and draw left/right edges
FOR y = 0 TO grid - 3
    total = 0
    FOR x = grid + 2 TO (grid * grid) - grid - 2 STEP grid
        IF tilef(y + x) = 1 THEN
            total = total + tilev(y + x)
        END IF
    NEXT
    IF tilev(y + x) = total THEN
        'if total match, highlight it
        LINE (tilex(y + x), tiley(y + x))-(tilex(y + x) + tilesize, tiley(y + x) + tilesize), _RGB(255, 255, 255), BF 'right side
        IF LEN(LTRIM$(STR$(tilev(y + x)))) = 2 THEN
            PPRINT tilex(y + x) + (tilesize / 2.5), tiley(y + x) + (tilesize / 3.2), tilesize / 3, _RGB(32, 32, 32), 0, LTRIM$(STR$(tilev(y + x)))
        ELSE
            PPRINT tilex(y + x) + (tilesize / 1.7), tiley(y + x) + (tilesize / 3.2), tilesize / 3, _RGB(32, 32, 32), 0, LTRIM$(STR$(tilev(y + x)))
        END IF
        LINE (tilex(y + x) - (tilex(y + x)), tiley(y + x))-((tilex(y + x) - (tilex(y + x))) + tilesize, tiley(y + x) + tilesize), _RGB(255, 255, 255), BF 'left side
        IF LEN(LTRIM$(STR$(tilev(y + x)))) = 2 THEN
            PPRINT tilex(y + x) - tilex(y + x) + (tilesize / 2.5), tiley(y + x) + (tilesize / 3.2), tilesize / 3, _RGB(32, 32, 32), 0, LTRIM$(STR$(tilev(y + x)))
        ELSE
            PPRINT tilex(y + x) - tilex(y + x) + (tilesize / 1.7), tiley(y + x) + (tilesize / 3.2), tilesize / 3, _RGB(32, 32, 32), 0, LTRIM$(STR$(tilev(y + x)))
        END IF

    ELSE
        'total doesnt match, don't highlight edge
        win = 0
        LINE (tilex(y + x), tiley(y + x))-(tilex(y + x) + tilesize, tiley(y + x) + tilesize), _RGB(77, 77, 77), BF 'right side
        LINE (tilex(y + x), tiley(y + x))-(tilex(y + x) + tilesize, tiley(y + x) + tilesize), _RGB(196, 196, 196), B 'right side
        IF LEN(LTRIM$(STR$(tilev(y + x)))) = 2 THEN
            PPRINT tilex(y + x) + (tilesize / 2.5), tiley(y + x) + (tilesize / 3.2), tilesize / 3, _RGB(196, 196, 196), 0, LTRIM$(STR$(tilev(y + x)))
        ELSE
            PPRINT tilex(y + x) + (tilesize / 1.7), tiley(y + x) + (tilesize / 3.2), tilesize / 3, _RGB(195, 196, 196), 0, LTRIM$(STR$(tilev(y + x)))
        END IF
        LINE (tilex(y + x) - (tilex(y + x)), tiley(y + x))-((tilex(y + x) - (tilex(y + x))) + tilesize, tiley(y + x) + tilesize), _RGB(77, 77, 77), BF 'left side
        LINE (tilex(y + x) - (tilex(y + x)), tiley(y + x))-((tilex(y + x) - (tilex(y + x))) + tilesize, tiley(y + x) + tilesize), _RGB(196, 196, 196), B 'left side
        IF LEN(LTRIM$(STR$(tilev(y + x)))) = 2 THEN
            PPRINT tilex(y + x) - tilex(y + x) + (tilesize / 2.5), tiley(y + x) + (tilesize / 3.2), tilesize / 3, _RGB(196, 196, 196), 0, LTRIM$(STR$(tilev(y + x)))
        ELSE
            PPRINT tilex(y + x) - tilex(y + x) + (tilesize / 1.7), tiley(y + x) + (tilesize / 3.2), tilesize / 3, _RGB(196, 196, 196), 0, LTRIM$(STR$(tilev(y + x)))
        END IF

    END IF

NEXT

'compute and draw top/bottom edges
FOR x = grid + 2 TO (grid * grid) - grid - 2 STEP grid
    total = 0
    FOR y = 0 TO grid - 3
        IF tilef(y + x) = 1 THEN
            total = total + tilev(y + x)
        END IF
    NEXT
    IF tilev(y + x) = total THEN
        'total matches, hight top/bottom
        LINE (tilex(y + x), tiley(y + x))-(tilex(y + x) + tilesize, tiley(y + x) + tilesize), _RGB(255, 255, 255), BF 'bottom
        IF LEN(LTRIM$(STR$(tilev(y + x)))) = 2 THEN
            PPRINT tilex(y + x) + (tilesize / 2.5), tiley(y + x) + (tilesize / 3.2), tilesize / 3, _RGB(32, 32, 32), 0, LTRIM$(STR$(tilev(y + x)))
        ELSE
            PPRINT tilex(y + x) + (tilesize / 1.7), tiley(y + x) + (tilesize / 3.2), tilesize / 3, _RGB(32, 32, 32), 0, LTRIM$(STR$(tilev(y + x)))
        END IF
        LINE (tilex(y + x), tiley(y + x) - tiley(y + x))-(tilex(y + x) + tilesize, (tiley(y + x) - tiley(y + x)) + tilesize), _RGB(255, 255, 255), BF 'top
        IF LEN(LTRIM$(STR$(tilev(y + x)))) = 2 THEN
            PPRINT tilex(y + x) + (tilesize / 2.5), tiley(y + x) - tiley(y + x) + (tilesize / 3.2), tilesize / 3, _RGB(32, 32, 32), 0, LTRIM$(STR$(tilev(y + x)))
        ELSE
            PPRINT tilex(y + x) + (tilesize / 1.7), tiley(y + x) - tiley(y + x) + (tilesize / 3.2), tilesize / 3, _RGB(32, 32, 32), 0, LTRIM$(STR$(tilev(y + x)))
        END IF
    ELSE
        'doesnt' match, dont hightlight top/bottom edges
        win = 0
        LINE (tilex(y + x), tiley(y + x))-(tilex(y + x) + tilesize, tiley(y + x) + tilesize), _RGB(77, 77, 77), BF 'bottom
        LINE (tilex(y + x), tiley(y + x))-(tilex(y + x) + tilesize, tiley(y + x) + tilesize), _RGB(196, 196, 196), B 'bottom
        IF LEN(LTRIM$(STR$(tilev(y + x)))) = 2 THEN
            PPRINT tilex(y + x) + (tilesize / 2.5), tiley(y + x) + (tilesize / 3.2), tilesize / 3, _RGB(196, 196, 196), 0, LTRIM$(STR$(tilev(y + x)))
        ELSE
            PPRINT tilex(y + x) + (tilesize / 1.7), tiley(y + x) + (tilesize / 3.2), tilesize / 3, _RGB(196, 196, 196), 0, LTRIM$(STR$(tilev(y + x)))
        END IF
        LINE (tilex(y + x), tiley(y + x) - tiley(y + x))-(tilex(y + x) + tilesize, (tiley(y + x) - tiley(y + x)) + tilesize), _RGB(77, 77, 77), BF 'top
        LINE (tilex(y + x), tiley(y + x) - tiley(y + x))-(tilex(y + x) + tilesize, (tiley(y + x) - tiley(y + x)) + tilesize), _RGB(196, 196, 196), B 'top
        IF LEN(LTRIM$(STR$(tilev(y + x)))) = 2 THEN
            PPRINT tilex(y + x) + (tilesize / 2.5), tiley(y + x) - tiley(y + x) + (tilesize / 3.2), tilesize / 3, _RGB(196, 196, 196), 0, LTRIM$(STR$(tilev(y + x)))
        ELSE
            PPRINT tilex(y + x) + (tilesize / 1.7), tiley(y + x) - tiley(y + x) + (tilesize / 3.2), tilesize / 3, _RGB(196, 196, 196), 0, LTRIM$(STR$(tilev(y + x)))
        END IF

    END IF
NEXT



'see if puzzle completed
IF win = 1 THEN
    PLAY "mbl16o2cdedefgabagfedc"

    'remove unused numbers
    FOR T = grid + 2 TO (grid * grid) - grid - 1
        'skip the outside rows
        FOR tt = 1 TO grid * grid STEP grid
            IF T = tt THEN GOTO skipcheck
            IF T = tt + (grid - 1) THEN GOTO skipcheck
        NEXT
        IF tilef(T) = 0 THEN
            LINE (tilex(T), tiley(T))-(tilex(T) + tilesize, tiley(T) + tilesize), _RGB(77, 77, 77), BF
        END IF
        skipcheck:
    NEXT

    _DELAY 5

    IF grid < 15 THEN grid = grid + 1

    GOTO top
END IF


RETURN


SUB PPRINT (x, y, SquareSize, clr&, trans&, text$)
    orig& = _DEST
    bit = 32: IF _PIXELSIZE(0) = 1 THEN bit = 256
    FOR t = 0 TO LEN(text$) - 1
        pprintimg& = _NEWIMAGE(16, 16, bit)
        _DEST pprintimg&
        CLS , trans&: COLOR clr&
        PRINT MID$(text$, t + 1, 1);
        _CLEARCOLOR _RGB(0, 0, 0), pprintimg&
        _DEST orig&
        x1 = x + (t * SquareSize): x2 = x1 + SquareSize
        y1 = y: y2 = y + SquareSize
        _PUTIMAGE (x1 - (SquareSize / 2), y1)-(x2, y2 + (SquareSize / 3)), pprintimg&
        _FREEIMAGE pprintimg&
    NEXT
END SUB

Print this item

  Print Using?
Posted by: james2464 - 11-01-2022, 11:39 PM - Forum: Help Me! - Replies (42)

For a clock timer, I'm just wondering if there's a way to make the leading 0 appear when the time is 1:05 etc  (I'm getting 1: 5)

I'm using the "Print Using" command, and it's fine otherwise, but I'd like that zero to be there.

Code: (Select All)
'timer

s = 0
Do
    _Limit 5
    Cls
    s = s + 1
    If s > 59 Then
        m = m + 1
        s = 0
    End If
    tmp$ = "##:##"
    Locate 1, 1
    Print Using tmp$; m; s
    _Display
Loop

Print this item

  beginning of spacewar! / asteroids type game
Posted by: madscijr - 11-01-2022, 09:10 PM - Forum: Works in Progress - No Replies

This started as an old VB6 Asteroids type game by someone named Tassadar that I downloaded from planetsourcecode.com years ago, and converted to QB64. 

It's very easy to understand and modify to make something more interesting. 

The next thing I would like to figure out is how to change how it handles x/y velocity for smoother more natural acceleration / momentum.

Enjoy

Code: (Select All)
_Title "Collisions"

' Original VB6 code for "Collisions" by Tassadar,
' found on PlanetSourceCode.com in 2001.
' Converted to QB64 by madscijr.

' DATE         WHO-DONE-IT   DID-WHAT
' 2001-06-10   Tassadar      created program
' 2022-11-01   madscijr      v0.19, got it working in QB64, added some tweaks

' DONE:
' * get it working in QB64 + tweak various calculations
' * random enemy radius (wider range with each level)
' * added variables for bullet lifespan, bullet radius
' * bullets can wrap around screen
' * bullet-bullet collisions
' * bonus shields at end of round
' * are you lookin' at me??

' TODO:
' * track x/y velocity differently (movement is jerky)
' * option: enemies can shoot each other
' * option: fire button auto-repeat
' * support simple polygon shapes
' * improve collisions
' * support higher resolution upto 4k to fit lots of stuff on screen
' * local multiplayer Spacewar! (upto 16 players)
' * sound effects
' * explosions and stuff
' * menu + customize input + options
' * sun and gravity
' * asteroids
' * enemies can move + enemy AI
' * vary difficulty + scoring based on difficulty
' * limited fuel/ammo
' * player must dock with space station for more fuel/ammo/repair
' * gravity for large asteroids/moons
' * lunar lander mode, players land moons/asteroids to refuel, etc.
' * attack opponents' bases, etc.
' * etc.

' ORIGINAL CREDITS:
'********************************************************************************
'*            This is My Program, I made it to show some trig stuff             *
'* and got a little carried away, but it shows how to do trigonometry anyways   *
'*                                                                              *
'*    You are permitted to do whatever you want with this code                  *
'*        eg. Feel free to modify this code, steal it, etc.                     *
'*               I don't really give a crap!                                    *
'*                                                                              *
'* Programmed by Tassadar                                                       *
'********************************************************************************

' USEFUL CONSTANTS
Const FALSE = 0
Const TRUE = Not FALSE
'Const Pi = 3.14159 ' Pi , bit obvious

Const iFPS = 60 ' Delay between frames (frames per second)

Const SHIP_RADIUS = 10 ' What is the radius of the players ship
Const BULLET_RADIUS = 2 ' What is the radius of the bullets
Const SHIP_ACCEL = 0.05 ' 0.1 ' how fast does it accelerate
Const MAX_SPEED = 12 ' 6 ' what is the ships max speed
Const SHOOT_DELAY = 200 ' Delay between shots for the ship
Const BULLET_SPEED = 9 ' 6 ' Bullet speed - Ship speed + bullet speed = overall bulletspeed
Const BULLET_LIFESPAN = 1 ' # seconds bullet is alive
Const BULLETS_STOP_BULLETS = TRUE

Const TURN_SPEED = 72 ' 36=faster 18=superfast

Const MIN_ENEMY_RADIUS = 6 ' What is the initial minimum radius of the enemy ships
Const MAX_ENEMY_RADIUS = 99 ' What is the initial maximum radius of the enemy ships

Const iMinX = 1
Const iMaxX = 800
Const iMinY = 1
Const iMaxY = 640

Const BULLET_DAMAGE = 5
Const WRAP_BULLET = TRUE
Const BONUS_SHIELDS = 50

Type ShipType
    xPos As Integer ' X co-ordinate of the ship
    yPos As Integer ' Y co-ordinate of the ship
    heading As Single ' which direction is the ship heading
    facing As Single ' which direction is the ship facing
    shields As Integer ' how much shields does the ship have
    speed As Single ' how fast is the ship going
    ShootTime As Long
    ShootCount As Long
End Type ' ShipType

Type EnemyType
    xPos As Integer ' X position of this enemy
    yPos As Integer ' Y position of this enemy
    life As Integer ' How much life does this enemy have
    alive As Integer ' Is this enemy alive
    radius As Integer ' size of enemy ship
End Type ' EnemyType

Type BulletType
    xPos As Integer ' X co-ordinate of this bullet
    yPos As Integer ' Y co-ordinate of this bullet
    heading As Single ' Direction this bullet is heading
    speed As Single ' Speed of this bullet
    alive As Integer ' Is this bullet alive
    kind As String ' What type of bullet is this (Players or enemies)
    lifespan As Long
    lifetime As Long
End Type ' BulletType

'' ENABLE / DISABLE DEBUG CONSOLE
'Dim Shared m_bDebug As Integer: m_bDebug = TRUE

' BASIC PROGRAM METADATA
Dim Shared m_ProgramPath$: m_ProgramPath$ = Left$(Command$(0), _InStrRev(Command$(0), "\"))
Dim Shared m_ProgramName$: m_ProgramName$ = Mid$(Command$(0), _InStrRev(Command$(0), "\") + 1)

' OTHER USEFUL VARIABLES
Dim Shared PI As Single: PI = 4 * Atn(1)
Dim Shared ENGINE_RADIUS As Integer: ENGINE_RADIUS = SHIP_RADIUS * 0.6
Dim Shared FLAME_RADIUS As Integer: FLAME_RADIUS = SHIP_RADIUS * 2

' GAME STATE
Dim Shared m_bGameOver As Integer ' Is the game over
Dim Shared m_bAllDead As Integer ' Are all the enemies dead
Dim Shared m_iLevel As Integer ' Track the level
Dim Shared m_iScore As Integer ' Keeps track of player score
Dim Shared m_iMinEnemyRadius As Integer ' current minimum enemy radius
Dim Shared m_iMaxEnemyRadius As Integer ' current maximum enemy radius

' INPUT VARIABLES
Dim Shared m_bLeftKey As Integer ' Is the LeftKey depressed
Dim Shared m_bRightKey As Integer ' Is the RightKey depressed
Dim Shared m_bUpKey As Integer ' Is the UpKey depressed
Dim Shared m_bDownKey As Integer ' Is the DownKey depressed
Dim Shared m_bShootKey As Integer ' Is the ShootKey depressed
Dim Shared m_bCheatKey As Integer
Dim Shared m_bEscKey As Integer

' GAME OBJECTS
Dim Shared m_Ship As ShipType ' The Players Ship
ReDim Shared m_arrEnemy(-1) As EnemyType ' A nice array of enemies
ReDim Shared m_arrBullet(-1) As BulletType ' A nice array of Bullets

'' ****************************************************************************************************************************************************************
'' ACTIVATE DEBUGGING WINDOW
'If m_bDebug = TRUE Then
'    $Console
'    _Delay 4
'    _Console On
'    _Echo "Started " + m_ProgramName$
'    _Echo "Debugging on..."
'End If
'' ****************************************************************************************************************************************************************

' START THE GAME
main

' FINISHED
System ' return control to the operating system

'' ****************************************************************************************************************************************************************
'' DEACTIVATE DEBUGGING WINDOW
'If m_bDebug = TRUE Then
'    _Console Off
'End If
' ****************************************************************************************************************************************************************

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' BEGIN GAME CODE
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' /////////////////////////////////////////////////////////////////////////////

Sub main ()
    Dim RoutineName As String: RoutineName = "main"

    Screen _NewImage(iMaxX, iMaxY, 32) ' 100 text columns x 40 text rows
    _KeyClear
   
    InitVariables ' Initialize variables
   
    Do ' main game loop
        Cls ' Clear the form
        GetInput
        If m_bEscKey = FALSE Then
            MoveBullets ' Activates the MoveBullets sub
            MoveShip ' Activates the MoveShip sub
            MoveEnemy ' (doesn't do much yet)
            Collisions ' Activates the Collisions sub
        Else
            m_bGameOver = TRUE
        End If
       
        If m_bGameOver = FALSE Then
            Shooting ' Activates the Shooting sub
            DrawEnemy ' Activates the DrawEnemy sub
            DrawBullets ' Activates the DrawBullets sub
            ShowScore ' Display the score, etc.
            DrawShip ' Activates the DrawShip sub
            Respawn ' Activates the Respawn sub
        Else
            If AskPlayAgain% = TRUE Then
                InitVariables
            Else
                Exit Do
            End If
        End If

        ' UPDATE THE SCREEN
        _Display
       
        ' CONTROL GAME SPEED
        _Limit iFPS
    Loop
   
    ' RETURN TO AUTODISPLAY
    _AutoDisplay
   
End Sub ' main

' /////////////////////////////////////////////////////////////////////////////

Function AskPlayAgain%
    Dim bResult As Integer
    Dim in$
   
    Cls
    Print "GAME OVER"
    Print
    Print "Level: " + cstr$(m_iLevel)
    Print "Score: " + cstr$(m_iScore)
    Print
    Do
        Input "Do you wish to try again (y/n) "; in$
        If LCase$(_Trim$(in$)) = "y" Then
            bResult = TRUE
            Exit Do
        ElseIf LCase$(_Trim$(in$)) = "n" Then
            bResult = FALSE
            Exit Do
        Else
            Print
            Print "Please type 'y' or 'n'"
            Print
        End If
    Loop
   
    AskPlayAgain% = bResult
End Function ' AskPlayAgain%

' /////////////////////////////////////////////////////////////////////////////
' Set the initial state for variables

Sub InitVariables ()
    Dim iLoop1 As Integer ' Used for variables
    Dim iSpread As Integer
    Dim iHalf As Integer
    Dim iDivisor As Integer
   
    ' Msgbox telling you how to play
    'MsgBox "Use the arrow keys to fly around" + vbCrLf + "Control to shoot", vbOKOnly, "How To Play"
   
    ' Score
    m_iLevel = 1
    m_iScore = 0
   
    ' Game status
    m_bGameOver = FALSE
   
    ' Enemy min/max radius
    iSpread = MAX_ENEMY_RADIUS - MIN_ENEMY_RADIUS
    iHalf = iSpread / 2
    iDivisor = iSpread / 10
    m_iMinEnemyRadius = iHalf - iDivisor
    If m_iMinEnemyRadius < MIN_ENEMY_RADIUS Then m_iMinEnemyRadius = MIN_ENEMY_RADIUS
    m_iMaxEnemyRadius = iHalf + iDivisor
    If m_iMaxEnemyRadius > MAX_ENEMY_RADIUS Then m_iMaxEnemyRadius = MAX_ENEMY_RADIUS
   
    ' Clear input flags
    m_bLeftKey = FALSE
    m_bRightKey = FALSE
    m_bUpKey = FALSE
    m_bDownKey = FALSE
    m_bShootKey = FALSE
   
    ' Set the starting positions of the ship
    m_Ship.heading = 0
    m_Ship.facing = 0
    m_Ship.shields = 100
    m_Ship.speed = 0
    m_Ship.xPos = RandomNumber%(iMinX, iMaxX) 'Int(Rnd * ScaleWidth + 1)
    m_Ship.yPos = RandomNumber%(iMinY, iMaxY) 'Int(Rnd * ScaleHeight + 1)
    m_Ship.ShootTime = iFPS \ 4
    m_Ship.ShootCount = m_Ship.ShootTime + 1
   
    ' Spawn enemy
    ReDim _Preserve m_arrEnemy(0) As EnemyType
    For iLoop1 = 0 To UBound(m_arrEnemy)
        ' Set the starting position of the enemies
        m_arrEnemy(iLoop1).alive = TRUE
        m_arrEnemy(iLoop1).life = 30
        m_arrEnemy(iLoop1).xPos = m_Ship.xPos
        m_arrEnemy(iLoop1).yPos = m_Ship.yPos
       
        ' choose a random size
        m_arrEnemy(iLoop1).radius = RandomNumber%(m_iMinEnemyRadius, m_iMaxEnemyRadius)
       
        ' Stops the enemy starting on top of the ship
        Do Until GetDist(m_arrEnemy(iLoop1).xPos, m_arrEnemy(iLoop1).yPos, m_Ship.xPos, m_Ship.yPos) > SHIP_RADIUS * 10
            m_arrEnemy(iLoop1).xPos = RandomNumber%(iMinX, iMaxX) 'Int(Rnd * ScaleWidth + 1)
            m_arrEnemy(iLoop1).yPos = RandomNumber%(iMinY, iMaxY) 'Int(Rnd * ScaleHeight + 1)
        Loop
    Next iLoop1
   
    ' RESET BULLETS
    ReDim _Preserve m_arrBullet(-1) As BulletType
End Sub ' InitVariables

' /////////////////////////////////////////////////////////////////////////////
' Detect which keys are pressed

Sub GetInput ()
    While _DeviceInput(1): Wend ' clear and update the keyboard buffer
   
    If _Button(KeyCode_Left%) Then
        m_bLeftKey = TRUE
        m_bRightKey = FALSE
    ElseIf _Button(KeyCode_Right%) Then
        m_bLeftKey = FALSE
        m_bRightKey = TRUE
    Else
        m_bLeftKey = FALSE
        m_bRightKey = FALSE
    End If
   
    If _Button(KeyCode_Up%) Then
        m_bUpKey = TRUE
        m_bDownKey = FALSE
    ElseIf _Button(KeyCode_Down%) Then
        m_bUpKey = FALSE
        m_bDownKey = TRUE
    Else
        m_bUpKey = FALSE
        m_bDownKey = FALSE
    End If

    If _Button(KeyCode_CtrlLeft%) Then
        m_bShootKey = TRUE
    ElseIf _Button(KeyCode_CtrlRight%) Then
        m_bShootKey = TRUE
    Else
        m_bShootKey = FALSE
    End If

    If _Button(KeyCode_1%) Then
        m_bCheatKey = TRUE
    Else
        m_bCheatKey = FALSE
    End If
   
    If _Button(KeyCode_Escape%) Then
        m_bEscKey = TRUE
    Else
        m_bEscKey = FALSE
    End If
   
    ' CLEAR KEYBOARD BUFFER
    _KeyClear

End Sub ' GetInput

' /////////////////////////////////////////////////////////////////////////////
' Check for collisions

' TODO: improve collision checking to handle different shape polygons, etc.

Sub Collisions ()
    Dim iLoop1 As Integer
    Dim iLoop2 As Integer
    Dim in$
   
    ' Check for bullet collisions
    For iLoop1 = 0 To UBound(m_arrBullet)
       
        ' IS THIS BULLET ALIVE?
        If m_arrBullet(iLoop1).alive = TRUE Then
           
            ' CHECK FOR BULLET HIT BULLET
            For iLoop2 = 0 To UBound(m_arrBullet)
                If iLoop2 <> iLoop1 Then
                    If BULLETS_STOP_BULLETS = TRUE Then
                        If GetDist(m_arrBullet(iLoop2).xPos, m_arrBullet(iLoop2).yPos, m_arrBullet(iLoop1).xPos, m_arrBullet(iLoop1).yPos) <= BULLET_RADIUS Then
                            ' BOTH SHOTS DESTROYED
                            m_arrBullet(iLoop1).alive = FALSE ' Destroy the bullet
                            m_arrBullet(iLoop2).alive = FALSE ' Destroy the other bullet
                        End If
                    End If
                End If
            Next iLoop2
        End If
       
        ' IS THIS BULLET STILL ALIVE?
        If m_arrBullet(iLoop1).alive = TRUE Then
           
            ' CHECK ENEMY BULLET
            If m_arrBullet(iLoop1).kind = "ENEMY" Then
                ' Check for collision between bullet and ship
                If GetDist(m_Ship.xPos, m_Ship.yPos, m_arrBullet(iLoop1).xPos, m_arrBullet(iLoop1).yPos) <= SHIP_RADIUS Then
                    m_Ship.shields = m_Ship.shields - BULLET_DAMAGE ' Take Damage
                    m_arrBullet(iLoop1).alive = FALSE ' Destroy the bullet
                End If
            Else
                ' CHECK FOR PLAYER'S BULLET
                If m_arrBullet(iLoop1).kind = "SHIP" Then
                    For iLoop2 = 0 To UBound(m_arrEnemy)
                        ' If the enemy is alive then
                        If m_arrEnemy(iLoop2).alive = TRUE Then
                            ' Check for collision between bullet and enemy
                            If GetDist(m_arrEnemy(iLoop2).xPos, m_arrEnemy(iLoop2).yPos, m_arrBullet(iLoop1).xPos, m_arrBullet(iLoop1).yPos) <= m_arrEnemy(iLoop2).radius Then
                                m_arrEnemy(iLoop2).life = m_arrEnemy(iLoop2).life - BULLET_DAMAGE ' Enemy take damage
                                m_arrBullet(iLoop1).alive = FALSE ' Destroy the bullet
                            End If
                        End If
                    Next iLoop2
                End If
            End If
        End If
    Next iLoop1
   
    ' CHECK FOR SHIP COLLIDING WITH ENEMY
    For iLoop1 = 0 To UBound(m_arrEnemy)
        ' If the enemy is alive then
        If m_arrEnemy(iLoop1).alive = TRUE Then
            ' Check for collision between ship and enemy
            If GetDist(m_Ship.xPos, m_Ship.yPos, m_arrEnemy(iLoop1).xPos, m_arrEnemy(iLoop1).yPos) <= m_arrEnemy(iLoop1).radius Then
                m_arrEnemy(iLoop1).life = 0 ' The enemy has no life/Dead
                m_Ship.shields = 0 ' The ship has no shields/Dead
            End If
           
            ' if the enemy is dead then destroy it, add to score
            If m_arrEnemy(iLoop1).life <= 0 Then
                m_arrEnemy(iLoop1).alive = FALSE
                m_iScore = m_iScore + 10
            End If
        End If
    Next iLoop1
   
    ' IS SHIP DEAD?
    If m_Ship.shields <= 0 Then
        '' Display the message box
        'iLoop1 = MsgBox("You have a score of " & m_iScore & vbCrLf & vbCrLf & "Do you wish to try again?", vbYesNo, "Try Again")
        'Select Case iLoop1
        '    Case vbYes
        '        ' Restart if yes is clicked
        '        InitVariables
        '    Case vbNo
        '        ' End if no clicked
        '        End
        'End Select
        m_bGameOver = TRUE
    End If
End Sub ' Collisions

' /////////////////////////////////////////////////////////////////////////////

Sub Shooting ()
    Dim iLoop1 As Integer ' Used for variables
    Dim iLoop2 As Integer ' Used for variables
    Dim iFreeSpot As Integer
    Dim sngXComp As Single
    Dim sngYComp As Single

    ' DID PLAYER SHOOT?
    If m_bShootKey = TRUE Then
        ' Has the gun cooled down yet (prevent bullet being created every 25 milliseconds)
       
        If m_Ship.ShootCount > m_Ship.ShootTime Then
            m_Ship.ShootCount = 0
           
            iFreeSpot = -1
           
            For iLoop1 = 0 To UBound(m_arrBullet)
                ' Check whether it can use another bullet or not
                If m_arrBullet(iLoop1).alive = FALSE Then
                    ' if so use the dead bullet
                    iFreeSpot = iLoop1
                    Exit For
                End If
            Next iLoop1
            ' if there were no already dead bullets
            If iFreeSpot = -1 Then
                ' create another one
                ReDim _Preserve m_arrBullet(UBound(m_arrBullet) + 1) As BulletType

                ' iFreeSpot is this new bullet
                iFreeSpot = UBound(m_arrBullet)
            End If

            ' Set the properties of this bullet
            m_arrBullet(iFreeSpot).alive = TRUE ' The bullet is alive
            m_arrBullet(iFreeSpot).xPos = m_Ship.xPos ' the bullet is created where the ship is
            m_arrBullet(iFreeSpot).yPos = m_Ship.yPos ' the bullet is created where the ship is
            m_arrBullet(iFreeSpot).kind = "SHIP" ' This is a Ship Bullet
            m_arrBullet(iFreeSpot).lifespan = BULLET_LIFESPAN * iFPS ' # seconds bullet is alive
            m_arrBullet(iFreeSpot).lifetime = 0 ' bullet is brand new
           
            ' Determine the X and Y components of the resultant vector
            sngXComp = m_Ship.speed * Sin(m_Ship.heading) + BULLET_SPEED * Sin(m_Ship.facing)
            sngYComp = m_Ship.speed * Cos(m_Ship.heading) + BULLET_SPEED * Cos(m_Ship.facing)

            ' Determine the resultant speed
            m_arrBullet(iFreeSpot).speed = Sqr(sngXComp ^ 2 + sngYComp ^ 2)

            'Calculate the resultant heading, and adjust for arctangent by adding Pi if necessary
            If Sgn(sngYComp) > 0 Then
                m_arrBullet(iFreeSpot).heading = Atn(sngXComp / sngYComp)
            End If
            If Sgn(sngYComp) < 0 Then
                m_arrBullet(iFreeSpot).heading = Atn(sngXComp / sngYComp) + PI
            End If
        End If
    End If
   
    ' ENEMIES SHOOT
    For iLoop1 = 0 To UBound(m_arrEnemy)
        ' Check whether the enemy is alive
        If m_arrEnemy(iLoop1).alive = TRUE Then
            ' Check whether the enemy will fire or not
            If Int(Rnd * 100 + 1) = 1 Then
                iFreeSpot = -1
               
                For iLoop2 = 0 To UBound(m_arrBullet)
                    ' Check whether the enemy will use an old bullet
                    If m_arrBullet(iLoop2).alive = FALSE Then
                        ' If so iFreeSpot is the old bullet
                        iFreeSpot = iLoop2
                        Exit For
                    End If
                Next iLoop2
               
                ' If there were no free spots then create another bullet
                If iFreeSpot = -1 Then
                    ' Create the new bullet
                    ReDim _Preserve m_arrBullet(UBound(m_arrBullet) + 1) As BulletType
                    ' iFreeSpot is this new bullet
                    iFreeSpot = UBound(m_arrBullet)
                End If
               
                ' Set the properties for this bullet
                m_arrBullet(iFreeSpot).alive = TRUE ' It is Alive!!!
               
                ' Set it so the bullet shoots at the ship
                m_arrBullet(iFreeSpot).heading = GetAngle(m_arrEnemy(iLoop1).xPos, m_arrEnemy(iLoop1).yPos, m_Ship.xPos, m_Ship.yPos)
                m_arrBullet(iFreeSpot).xPos = m_arrEnemy(iLoop1).xPos ' Create the bullet where the enemy is
                m_arrBullet(iFreeSpot).yPos = m_arrEnemy(iLoop1).yPos ' Create the bullet where the enemy is
                m_arrBullet(iFreeSpot).speed = 6 ' Set the bullet speed
                m_arrBullet(iFreeSpot).kind = "ENEMY" ' This is an enemy bullet
                m_arrBullet(iFreeSpot).lifespan = BULLET_LIFESPAN * iFPS ' # seconds bullet is alive
                m_arrBullet(iFreeSpot).lifetime = 0 ' bullet is brand new
               
                ' Move bullet outside of enemy
                m_arrBullet(iFreeSpot).xPos = m_arrBullet(iFreeSpot).xPos + ((m_arrEnemy(iLoop1).radius + 1) * Sin(m_arrBullet(iFreeSpot).heading))
                m_arrBullet(iFreeSpot).yPos = m_arrBullet(iFreeSpot).yPos - ((m_arrEnemy(iLoop1).radius + 1) * Cos(m_arrBullet(iFreeSpot).heading))
               
            End If
        End If
    Next iLoop1
   
End Sub ' Shooting

' /////////////////////////////////////////////////////////////////////////////
' Draw the enemies

Sub DrawEnemy ()
    Dim iLoop1 As Integer ' Used for variables
    Dim iLoop2 As Integer ' Used for variables
    Dim iColor As _Unsigned Long
    Dim iX As Integer
    Dim iY As Integer
    Dim sngHeading As Single
    Dim iRadius As Integer
   
    For iLoop1 = 0 To UBound(m_arrEnemy)
        ' Is this enemy alive
        If m_arrEnemy(iLoop1).alive = TRUE Then
            ' Color based on damage
            If m_arrEnemy(iLoop1).life >= 30 Then
                iColor = cWhite
            ElseIf m_arrEnemy(iLoop1).life > 25 Then
                iColor = cYellow
            ElseIf m_arrEnemy(iLoop1).life > 20 Then
                iColor = cGold
            ElseIf m_arrEnemy(iLoop1).life > 15 Then
                iColor = cOrange
            ElseIf m_arrEnemy(iLoop1).life > 10 Then
                iColor = cDarkOrange
            ElseIf m_arrEnemy(iLoop1).life > 5 Then
                iColor = cOrangeRed
            Else
                iColor = cRed
            End If
           
            ' Draw body
            ' CIRCLE (x, y), radius, color
            'DrawCircleSolid iX, iY, 8, cRed
            Circle (m_arrEnemy(iLoop1).xPos, m_arrEnemy(iLoop1).yPos), m_arrEnemy(iLoop1).radius, iColor
           
            ' Draw "eye"
            sngHeading = GetAngle(m_arrEnemy(iLoop1).xPos, m_arrEnemy(iLoop1).yPos, m_Ship.xPos, m_Ship.yPos)
            iX = m_arrEnemy(iLoop1).xPos
            iY = m_arrEnemy(iLoop1).yPos
            iRadius = m_arrEnemy(iLoop1).radius / 3
            iX = iX + (m_arrEnemy(iLoop1).radius - iRadius) * Sin(sngHeading)
            iY = iY - (m_arrEnemy(iLoop1).radius - iRadius) * Cos(sngHeading)
            Circle (iX, iY), iRadius, iColor
           
        End If
    Next iLoop1
End Sub ' DrawEnemy

' /////////////////////////////////////////////////////////////////////////////
' Move Bullets

Sub MoveBullets ()
    Dim iLoop1 As Integer ' Used for variables
   
    If m_Ship.ShootCount <= m_Ship.ShootTime Then
        m_Ship.ShootCount = m_Ship.ShootCount + 1
    End If
   
    For iLoop1 = 0 To UBound(m_arrBullet)
        ' Is the bullet alive
        If m_arrBullet(iLoop1).alive = TRUE Then
            ' Move the bullets
            m_arrBullet(iLoop1).xPos = m_arrBullet(iLoop1).xPos + (m_arrBullet(iLoop1).speed * Sin(m_arrBullet(iLoop1).heading))
            m_arrBullet(iLoop1).yPos = m_arrBullet(iLoop1).yPos - (m_arrBullet(iLoop1).speed * Cos(m_arrBullet(iLoop1).heading))
           
            ' Did the bullet move off screen horizontally?
            If m_arrBullet(iLoop1).xPos < iMinX Then
                If WRAP_BULLET = TRUE Then
                    m_arrBullet(iLoop1).xPos = iMaxX
                Else
                    m_arrBullet(iLoop1).alive = FALSE ' Destroy the bullet
                End If
            ElseIf m_arrBullet(iLoop1).xPos > iMaxX Then
                If WRAP_BULLET = TRUE Then
                    m_arrBullet(iLoop1).xPos = iMinX
                Else
                    m_arrBullet(iLoop1).alive = FALSE ' Destroy the bullet
                End If
            End If
           
            ' Did the bullet move off screen vertically?
            If m_arrBullet(iLoop1).yPos < iMinY Then
                If WRAP_BULLET = TRUE Then
                    m_arrBullet(iLoop1).yPos = iMaxY
                Else
                    m_arrBullet(iLoop1).alive = FALSE ' Destroy the bullet
                End If
            ElseIf m_arrBullet(iLoop1).yPos > iMaxY Then
                If WRAP_BULLET = TRUE Then
                    m_arrBullet(iLoop1).yPos = iMinY
                Else
                    m_arrBullet(iLoop1).alive = FALSE ' Destroy the bullet
                End If
            End If
           
            ' Time how long bullet stays active
            m_arrBullet(iLoop1).lifetime = m_arrBullet(iLoop1).lifetime + 1
            If m_arrBullet(iLoop1).lifetime > m_arrBullet(iLoop1).lifespan Then
                m_arrBullet(iLoop1).alive = FALSE ' Destroy the bullet
            End If
           
        End If
    Next iLoop1
End Sub ' MoveBullets

' /////////////////////////////////////////////////////////////////////////////
' Draw the bullets

Sub DrawBullets ()
    Dim iLoop1 As Integer ' Used for variables
    For iLoop1 = 0 To UBound(m_arrBullet)
        ' Is the bullet alive
        If m_arrBullet(iLoop1).alive = TRUE Then
            If m_arrBullet(iLoop1).kind = "SHIP" Then
                ' Is this a ship bullet, draw a white bullet
                'Circle (m_arrBullet(iLoop1).xPos, m_arrBullet(iLoop1).yPos), 3, cWhite
                DrawCircleSolid m_arrBullet(iLoop1).xPos, m_arrBullet(iLoop1).yPos, BULLET_RADIUS, cWhite
               
            ElseIf m_arrBullet(iLoop1).kind = "ENEMY" Then
                ' if this is enemy bullet, draw a red bullet
                'Circle (m_arrBullet(iLoop1).xPos, m_arrBullet(iLoop1).yPos), 3, cOrangeRed
                DrawCircleSolid m_arrBullet(iLoop1).xPos, m_arrBullet(iLoop1).yPos, BULLET_RADIUS, cOrangeRed
            End If
        End If
    Next iLoop1
End Sub ' DrawBullets

' /////////////////////////////////////////////////////////////////////////////
' Move the ship
Sub MoveShip ()
    Dim sngXComp As Single
    Dim sngYComp As Single
   
    ' If the left key is pressed then rotate the ship left
    If m_bLeftKey = TRUE Then
        m_Ship.facing = m_Ship.facing - PI / TURN_SPEED
    End If
   
    ' If the Right key is pressed then rotate the ship right
    If m_bRightKey = TRUE Then
        m_Ship.facing = m_Ship.facing + PI / TURN_SPEED
    End If
   
    ' If the up key is pressed then and accelerate it in the direction the ship is facing
    If m_bUpKey = TRUE Then
        ' ****************************************************************************************************************************************************************
        ' ****************************************************************************************************************************************************************
        ' ****************************************************************************************************************************************************************
        ' TODO: fix this to make the movement more natural...
       
        ' Determine the X and Y components of the resultant vector
        sngXComp = m_Ship.speed * Sin(m_Ship.heading) + SHIP_ACCEL * Sin(m_Ship.facing)
        sngYComp = m_Ship.speed * Cos(m_Ship.heading) + SHIP_ACCEL * Cos(m_Ship.facing)
       
        ' Determine the resultant speed
        m_Ship.speed = Sqr(sngXComp ^ 2 + sngYComp ^ 2)
       
        ' Calculate the resultant heading, and adjust for arctangent by adding Pi if necessary
        If Sgn(sngYComp) > 0 Then
            m_Ship.heading = Atn(sngXComp / sngYComp)
        End If
        If Sgn(sngYComp) < 0 Then
            m_Ship.heading = Atn(sngXComp / sngYComp) + PI
        End If
       
        ' ****************************************************************************************************************************************************************
        ' ****************************************************************************************************************************************************************
        ' ****************************************************************************************************************************************************************
    End If
   
    ' If the down key is pressed then and accelerate the ship in the opposite direction it is facing
    If m_bDownKey = TRUE And m_Ship.speed > -MAX_SPEED Then
        ' Determine the X and Y components of the resultant vector
        sngXComp = m_Ship.speed * Sin(m_Ship.heading) - SHIP_ACCEL * Sin(m_Ship.facing)
        sngYComp = m_Ship.speed * Cos(m_Ship.heading) - SHIP_ACCEL * Cos(m_Ship.facing)
       
        ' Determine the resultant speed
        m_Ship.speed = Sqr(sngXComp ^ 2 + sngYComp ^ 2)
       
        ' Calculate the resultant heading, and adjust for actangent by adding Pi if necessary
        If Sgn(sngYComp) > 0 Then
            m_Ship.heading = Atn(sngXComp / sngYComp)
        End If
        If Sgn(sngYComp) < 0 Then
            m_Ship.heading = Atn(sngXComp / sngYComp) + PI
        End If
    End If
   
    ' Did player hit cheat key?
    If m_bCheatKey = TRUE Then
        m_Ship.shields = m_Ship.shields + 10
    End If
   
    ' Don't let the ship go faster then the max speed
    If m_Ship.speed > MAX_SPEED Then
        m_Ship.speed = MAX_SPEED
    End If
   
    ' Move the ship
    m_Ship.xPos = m_Ship.xPos + m_Ship.speed * Sin(m_Ship.heading)
    m_Ship.yPos = m_Ship.yPos - m_Ship.speed * Cos(m_Ship.heading)
   
    ' Keep the ship inside the form
    If m_Ship.xPos < iMinX Then
        m_Ship.xPos = iMaxX
    End If
    If m_Ship.xPos > iMaxX Then
        m_Ship.xPos = iMinX
    End If
    If m_Ship.yPos < iMinY Then
        m_Ship.yPos = iMaxY
    End If
    If m_Ship.yPos > iMaxY Then
        m_Ship.yPos = iMinY
    End If
End Sub ' MoveShip

' /////////////////////////////////////////////////////////////////////////////
' Placeholder

Sub MoveEnemy ()
    Dim iLoop1 As Integer
    'For iLoop1 = 0 To UBound(m_arrEnemy)
    '    ' Check whether the enemy is alive
    '    If m_arrEnemy(iLoop1).alive = TRUE Then
    '    End If
    'Next iLoop1
End Sub ' MoveEnemy

' /////////////////////////////////////////////////////////////////////////////

Sub ShowScore
    ' Draw background
    Color cBlue, cBlue
    PrintAt 0, 0, String$(120, " ")
   
    ' Place the text on the form
    Color cLime, cEmpty
    PrintAt 0, 10, "Shields: " + cstr$(m_Ship.shields) ' LeftPadString$(cstr$(m_Ship.shields), 5, " ")
   
    ' Title displays the players score
    'frmCollision.Caption = "Score: " & m_iScore
    Color cCyan, cEmpty
    PrintAt 0, 40, "Score: " + cstr$(m_iScore) ' LeftPadString$(cstr$(m_iScore), 10, " ")
   
    ' Display the level
    Color cWhite, cEmpty
    PrintAt 0, 70, "Level: " + cstr$(m_iLevel) ' LeftPadString$(cstr$(m_iScore), 10, " ")
   
    ' Show instructions
    Color cRed, cRed
    PrintAt 39, 0, String$(120, " ")
    Color cWhite, cEmpty
    PrintAt 39, 0, "CONTROLS: LEFT/RIGHT = TURN   UP/DOWN = FORWARD/BACK   CTRL=FIRE   1=ADD SHIELD (CHEAT)"
   
End Sub ' ShowScore

' /////////////////////////////////////////////////////////////////////////////
' Draw the ship

Sub DrawShip ()
    Dim intX1 As Integer
    Dim intY1 As Integer
    Dim intX2 As Integer
    Dim intY2 As Integer
    Dim intX3 As Integer
    Dim intY3 As Integer
   
    Dim intX1b As Integer ' engine flame ends here
    Dim intY1b As Integer ' engine flame ends here
    Dim intX2b As Integer ' back wall starts here on left
    Dim intY2b As Integer ' back wall starts here on left
    Dim intX3b As Integer ' back wall starts here on right
    Dim intY3b As Integer ' back wall starts here on right
   
    ' -----------------------------------------------------------------------------
    ' Set the coordinates of the ship
   
    ' front
    intX1 = m_Ship.xPos + SHIP_RADIUS * Sin(m_Ship.facing)
    intY1 = m_Ship.yPos - SHIP_RADIUS * Cos(m_Ship.facing)
   
    ' left rear
    intX2 = m_Ship.xPos + SHIP_RADIUS * Sin(m_Ship.facing + 2 * PI / 3)
    intY2 = m_Ship.yPos - SHIP_RADIUS * Cos(m_Ship.facing + 2 * PI / 3)
   
    ' left rear (3/4 of the way down)
    intX2b = m_Ship.xPos + ENGINE_RADIUS * Sin(m_Ship.facing + 2 * PI / 3)
    intY2b = m_Ship.yPos - ENGINE_RADIUS * Cos(m_Ship.facing + 2 * PI / 3)
   
    ' right rear
    intX3 = m_Ship.xPos + SHIP_RADIUS * Sin(m_Ship.facing + 4 * PI / 3)
    intY3 = m_Ship.yPos - SHIP_RADIUS * Cos(m_Ship.facing + 4 * PI / 3)

    ' right rear (3/4 of the way down)
    intX3b = m_Ship.xPos + ENGINE_RADIUS * Sin(m_Ship.facing + 4 * PI / 3)
    intY3b = m_Ship.yPos - ENGINE_RADIUS * Cos(m_Ship.facing + 4 * PI / 3)
   
    ' rear where engine flames end
    intX1b = m_Ship.xPos - FLAME_RADIUS * Sin(m_Ship.facing)
    intY1b = m_Ship.yPos + FLAME_RADIUS * Cos(m_Ship.facing)
    'm_Ship.facing = m_Ship.facing - Pi / 36
   
    ' -----------------------------------------------------------------------------
    ' Draw the ship
   
    ' Draw the left side
    Line (intX1, intY1)-(intX2, intY2), cWhite
   
    ' Draw the right side
    Line (intX1, intY1)-(intX3, intY3), cWhite
   
    ' Draw the rear / aft side
    If m_bUpKey Then
        ' Draw rear side
        Line (intX2b, intY2b)-(intX3b, intY3b), cSilver
       
        ' Engine is firing
        'Line (intX2b, intY2b)-(intX3b, intY3b), cOrangeRed
       
        ' Draw the flame left side
        Line (intX1b, intY1b)-(intX2b, intY2b), cOrangeRed
       
        ' Draw the flame right side
        Line (intX1b, intY1b)-(intX3b, intY3b), cOrangeRed
       
    Else
        ' Draw rear side
        Line (intX2b, intY2b)-(intX3b, intY3b), cSilver
    End If
   
End Sub ' DrawShip

' /////////////////////////////////////////////////////////////////////////////
' Respawn the enemies if there all dead

Sub Respawn ()
    Dim iLoop1 As Integer ' Used for variables

    ' Check if all enemies are dead
    m_bAllDead = TRUE
    For iLoop1 = 0 To UBound(m_arrEnemy)
        ' If an enemy is alive then
        If m_arrEnemy(iLoop1).alive = TRUE Then
            ' enemies aren't all dead
            m_bAllDead = FALSE
            Exit For
        End If
    Next iLoop1
   
    ' if all dead, respawn and create one more enemy
    ' advance to next level
    If m_bAllDead = TRUE Then
        ' INCREASE LEVEL
        m_iLevel = m_iLevel + 1
       
        ' GIVE PLAYER SOME BONUS SHIELDS
        m_Ship.shields = m_Ship.shields + BONUS_SHIELDS
       
        ' INCREASE THE NUMBER OF ENEMIES
        ReDim _Preserve m_arrEnemy(UBound(m_arrEnemy) + 1) As EnemyType
       
        ' INCREASE VARIETY OF ENEMY SIZES
        If m_iMinEnemyRadius > MIN_ENEMY_RADIUS Then
            m_iMinEnemyRadius = m_iMinEnemyRadius - 1
        End If
        If m_iMaxEnemyRadius < MAX_ENEMY_RADIUS Then
            m_iMaxEnemyRadius = m_iMaxEnemyRadius + 1
        End If
       
        ' SPAWN NEW WAVE OF ENEMIES
        For iLoop1 = 0 To UBound(m_arrEnemy)
            ' Set the starting positions
            m_arrEnemy(iLoop1).alive = TRUE
            m_arrEnemy(iLoop1).life = 30
            m_arrEnemy(iLoop1).xPos = m_Ship.xPos
            m_arrEnemy(iLoop1).yPos = m_Ship.yPos
           
            ' choose a random size
            m_arrEnemy(iLoop1).radius = RandomNumber%(m_iMinEnemyRadius, m_iMaxEnemyRadius)
           
            ' make sure the enemies don't start on the ship
            Do Until GetDist(m_arrEnemy(iLoop1).xPos, m_arrEnemy(iLoop1).yPos, m_Ship.xPos, m_Ship.yPos) > SHIP_RADIUS * 10
                m_arrEnemy(iLoop1).xPos = RandomNumber%(iMinX, iMaxX) 'Int(Rnd * ScaleWidth + 1)
                m_arrEnemy(iLoop1).yPos = RandomNumber%(iMinY, iMaxY) 'Int(Rnd * ScaleHeight + 1)
            Loop
        Next iLoop1
    End If
End Sub ' Respawn

' /////////////////////////////////////////////////////////////////////////////

Function GetDist! (sngX1 As Single, sngY1 As Single, sngX2 As Single, sngY2 As Single) ' As Single
    Dim sngXComp As Single
    Dim sngYComp As Single
   
    ' Set the X componate
    sngXComp = sngX2 - sngX1
   
    ' Set the Y Componate
    sngYComp = sngY1 - sngY2
   
    ' Get the distance between the two objects
    GetDist = Sqr(sngXComp ^ 2 + sngYComp ^ 2)
End Function ' GetDist

' /////////////////////////////////////////////////////////////////////////////

Function GetAngle (sngX1 As Single, sngY1 As Single, sngX2 As Single, sngY2 As Single)
    Dim sngXComp As Single
    Dim sngYComp As Single
   
    ' Set the X componate
    sngXComp = sngX2 - sngX1
   
    ' Set the Y componate
    sngYComp = sngY1 - sngY2
   
    ' Calculate the resultant angle, and adjust for actangent by adding Pi if necessary
    If Sgn(sngYComp) > 0 Then
        GetAngle = Atn(sngXComp / sngYComp)
    End If
    If Sgn(sngYComp) < 0 Then
        GetAngle = Atn(sngXComp / sngYComp) + PI
    End If
End Function ' GetAngle


' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' END GAME CODE
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' BEGIN TRIG FUNCTIONS
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' /////////////////////////////////////////////////////////////////////////////
' use angles in degrees units instead of radians (converted inside sub)
' Note this function uses whatever the default type is, better not be some Integer Type.
Function CosD (degrees)
    CosD = Cos(_D2R(degrees))
End Function ' CosD

' /////////////////////////////////////////////////////////////////////////////
' use angles in degrees units instead of radians (converted inside sub)
' Note this function uses whatever the default type is, better not be some Integer Type.
Function SinD (degrees)
    SinD = Sin(_D2R(degrees))
End Function ' SinD

' /////////////////////////////////////////////////////////////////////////////
' use angles in degrees units instead of radians (converted inside sub)
' Note this function uses whatever the default type is, better not be some Integer Type.
Function DAtan2 (x1, y1, x2, y2) ' The angle in degrees a 2nd point (x2, y2)  makes to a first point (x1, y1)
    ' Delta means change between 1 measure and another for example x2 - x1
    deltaX = x2 - x1
    deltaY = y2 - y1
   
    ' To find the angle point(x2, y2) makes to (x1, y1) in Degrees
    ' Take DegreeAngle = DAtan2(y2 - y1, x2 - x1)
    rtn = _R2D(_Atan2(deltaY, deltaX))
    If rtn < 0 Then
        DAtan2 = rtn + 360
    Else
        DAtan2 = rtn
    End If
End Function ' DAtan2

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' END TRIG FUNCTIONS
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++


' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' BEGIN GENERAL PURPOSE FUNCTIONS
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' /////////////////////////////////////////////////////////////////////////////
' Integer to string

Function cstr$ (myValue)
    'cstr$ = LTRIM$(RTRIM$(STR$(myValue)))
    cstr$ = _Trim$(Str$(myValue))
End Function ' cstr$

' /////////////////////////////////////////////////////////////////////////////
' Hack function, to be replaced with something better

Function DblToInt% (dblOld As Double)
    Dim dblNew As Double
    Dim sValue As String
    Dim iPos As Integer

    dblNew = RoundDouble#(dblOld, 0)
    'sValue = _Trim$(Str$(dblNew))

    sValue = DblToStr$(dblNew)

    'iPos = InStr(1, sValue, ".")
    'If iPos > 0 Then
    '    DblToInt% = Val(Left$(sValue, iPos - 1))
    'Else
    '    DblToInt% = Val(sValue)
    'End If

    DblToInt% = Val(sValue)
End Function ' DblToInt%

' /////////////////////////////////////////////////////////////////////////////
' Scientific notation - QB64 Wiki
' https://www.qb64.org/wiki/Scientific_notation

' Example: A string function that displays extremely small or large exponential decimal values.

Function DblToStr$ (n#)
    Dim result$: result$ = ""
    Dim value$
    Dim Xpos%
    Dim expo%
    Dim sign$
    Dim valu$
    Dim dot%
    Dim L%
    Dim add$
    Dim min$
    Dim DP$
    Dim n%

    value$ = UCase$(LTrim$(Str$(n#)))
    Xpos% = InStr(value$, "D") + InStr(value$, "E") ' only D or E can be present
    If Xpos% Then
        expo% = Val(Mid$(value$, Xpos% + 1))
        If Val(value$) < 0 Then
            sign$ = "-"
            valu$ = Mid$(value$, 2, Xpos% - 2)
        Else
            valu$ = Mid$(value$, 1, Xpos% - 1)
        End If
        dot% = InStr(valu$, ".")
        L% = Len(valu$)
        If expo% > 0 Then
            add$ = String$(expo% - (L% - dot%), "0")
        End If
        If expo% < 0 Then
            min$ = String$(Abs(expo%) - (dot% - 1), "0")
            DP$ = "."
        End If
        For n% = 1 To L%
            If Mid$(valu$, n%, 1) <> "." Then
                num$ = num$ + Mid$(valu$, n%, 1)
            End If
        Next n%
        result$ = _Trim$(sign$ + DP$ + min$ + num$ + add$)
    Else
        result$ = value$
    End If

    DblToStr$ = result$
End Function ' DblToStr$

' /////////////////////////////////////////////////////////////////////////////

Function DblRoundedToStr$ (dblValue As Double, intNumPlaces As Integer)
    Dim dblNew As Double
    dblNew = RoundDouble#(dblValue, intNumPlaces)
    DblRoundedToStr$ = DblToStr$(dblNew)
End Function ' DblRoundedToStr$

' /////////////////////////////////////////////////////////////////////////////

Function DoubleABS# (dblValue As Double)
    If Sgn(dblValue) = -1 Then
        DoubleABS# = 0 - dblValue
    Else
        DoubleABS# = dblValue
    End If
End Function ' DoubleABS#

' /////////////////////////////////////////////////////////////////////////////
' bplus: The Gold Standard is even better than THE QB64 CIRCLE sub in this respect!
' https://forum.qb64.org/index.php?topic=1044.135

' from Steve Gold standard
' Renamed fcirc to DrawCircleSolid

' Not as fast as DrawCircleTopLeft but pretty fast.

' Example:
' Screen _NewImage(800, 600, 32)
' _ScreenMove 250, 60
' For r = 250 To 0 Step -60
'     DrawCircleSolid 400, 300, r, _RGBA(255, 255, 255, 100)
' Next r

Sub DrawCircleSolid (CX As Integer, CY As Integer, R As Integer, C As _Unsigned Long)
    Dim Radius As Integer, RadiusError As Integer
    Dim X As Integer, Y As Integer

    Radius = Abs(R)
    RadiusError = -Radius
    X = Radius
    Y = 0

    If Radius = 0 Then PSet (CX, CY), C: Exit Sub

    ' Draw the middle span here so we don't draw it twice in the main loop,
    ' which would be a problem with blending turned on.
    Line (CX - X, CY)-(CX + X, CY), C, BF

    While X > Y
        RadiusError = RadiusError + Y * 2 + 1
        If RadiusError >= 0 Then
            If X <> Y + 1 Then
                Line (CX - Y, CY - X)-(CX + Y, CY - X), C, BF
                Line (CX - Y, CY + X)-(CX + Y, CY + X), C, BF
            End If
            X = X - 1
            RadiusError = RadiusError - X * 2
        End If
        Y = Y + 1
        Line (CX - X, CY - Y)-(CX + X, CY - Y), C, BF
        Line (CX - X, CY + Y)-(CX + X, CY + Y), C, BF
    Wend
End Sub ' DrawCircleSolid

' /////////////////////////////////////////////////////////////////////////////

Function FloatRoundedToStr$ (fValue As _Float, intNumPlaces As Integer)
    Dim fNew As _Float
    fNew = Round##(fValue, intNumPlaces)
    FloatRoundedToStr$ = FloatToStr$(fNew)
End Function ' DblRoundedToStr$

' /////////////////////////////////////////////////////////////////////////////
' Scientific notation - QB64 Wiki
' https://www.qb64.org/wiki/Scientific_notation

' Example: A string function that displays extremely small or large exponential decimal values.

Function FloatToStr$ (n##)
    value$ = UCase$(LTrim$(Str$(n##)))
    Xpos% = InStr(value$, "D") + InStr(value$, "E") 'only D or E can be present
    If Xpos% Then
        expo% = Val(Mid$(value$, Xpos% + 1))
        If Val(value$) < 0 Then
            sign$ = "-"
            valu$ = Mid$(value$, 2, Xpos% - 2)
        Else
            valu$ = Mid$(value$, 1, Xpos% - 1)
        End If
        dot% = InStr(valu$, ".")
        L% = Len(valu$)
        If expo% > 0 Then
            add$ = String$(expo% - (L% - dot%), "0")
        End If
        If expo% < 0 Then
            min$ = String$(Abs(expo%) - (dot% - 1), "0")
            DP$ = "."
        End If
        For n = 1 To L%
            If Mid$(valu$, n, 1) <> "." Then
                num$ = num$ + Mid$(valu$, n, 1)
            End If
        Next n
    Else
        FloatToStr$ = value$
        Exit Function
    End If
    FloatToStr$ = _Trim$(sign$ + DP$ + min$ + num$ + add$)
End Function ' FloatToStr$

' /////////////////////////////////////////////////////////////////////////////
' By sMcNeill from https://www.qb64.org/forum/index.php?topic=896.0

Function IsNum% (text$)
    Dim a$
    Dim b$
    a$ = _Trim$(text$)
    b$ = _Trim$(Str$(Val(text$)))
    If a$ = b$ Then
        IsNum% = TRUE
    Else
        IsNum% = FALSE
    End If
End Function ' IsNum%

' /////////////////////////////////////////////////////////////////////////////
' Split and join strings
' https://www.qb64.org/forum/index.php?topic=1073.0

'Combine all elements of in$() into a single string with delimiter$ separating the elements.

Function join$ (in$(), delimiter$)
    Dim result$
    Dim i As Long
    result$ = in$(LBound(in$))
    For i = LBound(in$) + 1 To UBound(in$)
        result$ = result$ + delimiter$ + in$(i)
    Next i
    join$ = result$
End Function ' join$

' /////////////////////////////////////////////////////////////////////////////

Function LeftPadString$ (myString$, toWidth%, padChar$)
    LeftPadString$ = Right$(String$(toWidth%, padChar$) + myString$, toWidth%)
End Function ' LeftPadString$

' /////////////////////////////////////////////////////////////////////////////
' ABS was returning strange values with type LONG
' so I created this which does not.

Function LongABS& (lngValue As Long)
    If Sgn(lngValue) = -1 Then
        LongABS& = 0 - lngValue
    Else
        LongABS& = lngValue
    End If
End Function ' LongABS&

' /////////////////////////////////////////////////////////////////////////////
' iRow% and iCol% are 0-based in this version

Sub PrintAt (iRow%, iCol%, sText$)
    '_PrintString (iCol% * 8, iRow% * 16), sText$
    _PrintString (iCol% * 8, iRow% * 16), sText$
    '_PrintString (iCol%, iRow%), sText$
End Sub ' PrintAt

' /////////////////////////////////////////////////////////////////////////////
' Generate random value between Min and Max.

Function RandomNumber% (Min%, Max%)
    Dim NumSpread%

    ' SET RANDOM SEED
    'Randomize ' Initialize random-number generator.
    Randomize Timer

    ' GET RANDOM # Min%-Max%
    'RandomNumber = Int((Max * Rnd) + Min) ' generate number

    NumSpread% = (Max% - Min%) + 1

    RandomNumber% = Int(Rnd * NumSpread%) + Min% ' GET RANDOM # BETWEEN Max% AND Min%

End Function ' RandomNumber%

' /////////////////////////////////////////////////////////////////////////////

Function RightPadString$ (myString$, toWidth%, padChar$)
    RightPadString$ = Left$(myString$ + String$(toWidth%, padChar$), toWidth%)
End Function ' RightPadString$

' /////////////////////////////////////////////////////////////////////////////
' https://www.qb64.org/forum/index.php?topic=3605.0
' Quote from: SMcNeill on Today at 03:53:48 PM
'
' Sometimes, you guys make things entirely too  complicated.
' There ya go!  Three functions to either round naturally,
' always round down, or always round up, to whatever number of digits you desire.
' EDIT:  Modified to add another option to round scientific,
' since you had it's description included in your example.

' Receives + returns _FLOAT myVar## (-1.18E-4932 to +1.18E+4932)

' =============================================================================
' ROUNDING FUNCTIONS FOR TYPE _FLOAT

Function Round## (num##, digits%)
    Round## = Int(num## * 10 ^ digits% + .5) / 10 ^ digits%
End Function

Function RoundUp## (num##, digits%)
    RoundUp## = _Ceil(num## * 10 ^ digits%) / 10 ^ digits%
End Function

Function RoundDown## (num##, digits%)
    RoundDown## = Int(num## * 10 ^ digits%) / 10 ^ digits%
End Function

Function RoundScientific## (num##, digits%)
    RoundScientific## = _Round(num## * 10 ^ digits%) / 10 ^ digits%
End Function

' =============================================================================
' ROUNDING FUNCTIONS FOR TYPE DOUBLE

Function RoundDouble# (num#, digits%)
    RoundDouble# = Int(num# * 10 ^ digits% + .5) / 10 ^ digits%
End Function

Function RoundUpDouble# (num#, digits%)
    RoundUpDouble# = _Ceil(num# * 10 ^ digits%) / 10 ^ digits%
End Function

Function RoundDownDouble# (num#, digits%)
    RoundDownDouble# = Int(num# * 10 ^ digits%) / 10 ^ digits%
End Function

Function RoundScientificDouble# (num#, digits%)
    RoundScientificDouble# = _Round(num# * 10 ^ digits%) / 10 ^ digits%
End Function

' =============================================================================
' ROUNDING FUNCTIONS FOR TYPE SINGLE

Function RoundSingle! (num!, digits%)
    RoundSingle! = Int(num! * 10 ^ digits% + .5) / 10 ^ digits%
End Function

' NOTE: not sure this one works: when digits%=3, it rounds .31 to .32
Function RoundUpSingle! (num!, digits%)
    RoundUpSingle! = _Ceil(num! * 10 ^ digits%) / 10 ^ digits%
End Function

Function RoundDownSingle! (num!, digits%)
    RoundDownSingle! = Int(num! * 10 ^ digits%) / 10 ^ digits%
End Function

Function RoundScientificSingle! (num!, digits%)
    RoundScientificSingle! = _Round(num! * 10 ^ digits%) / 10 ^ digits%
End Function

' /////////////////////////////////////////////////////////////////////////////

Function SmallestOf3% (i1%, i2%, i3%)
    Dim iMin%
    iMin% = i1%
    If i2% < iMin% Then iMin% = i2%
    If i3% < iMin% Then iMin% = i3%
    SmallestOf3% = iMin%
End Function ' SmallestOf3

' /////////////////////////////////////////////////////////////////////////////

Function SngRoundedToStr$ (sngValue As Single, intNumPlaces As Integer)
    Dim sngNew As Single
    sngNew = RoundSingle!(sngValue, intNumPlaces)
    SngRoundedToStr$ = SngToStr$(sngNew)
End Function ' SngRoundedToStr$

' /////////////////////////////////////////////////////////////////////////////
' Hack function, to be replaced with something better

Function SngToInt% (sngOld As Single)
    Dim sngNew As Single
    Dim sValue As String
    Dim iPos As Integer

    sngNew = RoundSingle!(sngOld, 0)
    'sValue = _Trim$(Str$(sngNew))

    sValue = SngToStr$(sngNew)

    'iPos = InStr(1, sValue, ".")
    'If iPos > 0 Then
    '    SngToInt% = Val(Left$(sValue, iPos - 1))
    'Else
    '    SngToInt% = Val(sValue)
    'End If

    SngToInt% = Val(sValue)
End Function ' SngToInt%

' /////////////////////////////////////////////////////////////////////////////
' Scientific notation - QB64 Wiki
' https://www.qb64.org/wiki/Scientific_notation

' Example: A string function that displays extremely small or large exponential decimal values.

Function SngToStr$ (n!)
    Dim result$: result$ = ""
    Dim value$
    Dim Xpos%
    Dim expo%
    Dim sign$
    Dim valu$
    Dim dot%
    Dim L%
    Dim add$
    Dim min$
    Dim DP$
    Dim n%

    value$ = UCase$(LTrim$(Str$(n!)))
    Xpos% = InStr(value$, "D") + InStr(value$, "E") ' only D or E can be present
    If Xpos% Then
        expo% = Val(Mid$(value$, Xpos% + 1))
        If Val(value$) < 0 Then
            sign$ = "-"
            valu$ = Mid$(value$, 2, Xpos% - 2)
        Else
            valu$ = Mid$(value$, 1, Xpos% - 1)
        End If
        dot% = InStr(valu$, ".")
        L% = Len(valu$)
        If expo% > 0 Then
            add$ = String$(expo% - (L% - dot%), "0")
        End If
        If expo% < 0 Then
            min$ = String$(Abs(expo%) - (dot% - 1), "0")
            DP$ = "."
        End If
        For n% = 1 To L%
            If Mid$(valu$, n%, 1) <> "." Then
                num$ = num$ + Mid$(valu$, n%, 1)
            End If
        Next n%
        result$ = _Trim$(sign$ + DP$ + min$ + num$ + add$)
    Else
        result$ = value$
    End If

    SngToStr$ = result$
End Function ' SngToStr$

' /////////////////////////////////////////////////////////////////////////////
' Split and join strings
' https://www.qb64.org/forum/index.php?topic=1073.0
'
' FROM luke, QB64 Developer
' Date: February 15, 2019, 04:11:07 AM
'
' Given a string of words separated by spaces (or any other character),
' splits it into an array of the words. I've no doubt many people have
' written a version of this over the years and no doubt there's a million
' ways to do it, but I thought I'd put mine here so we have at least one
' version. There's also a join function that does the opposite
' array -> single string.
'
' Code is hopefully reasonably self explanatory with comments and a little demo.
' Note, this is akin to Python/JavaScript split/join, PHP explode/implode.

'Split in$ into pieces, chopping at every occurrence of delimiter$. Multiple consecutive occurrences
'of delimiter$ are treated as a single instance. The chopped pieces are stored in result$().
'
'delimiter$ must be one character long.
'result$() must have been REDIMmed previously.

' Modified to handle multi-character delimiters

Sub split (in$, delimiter$, result$())
    Dim start As Integer
    Dim finish As Integer
    Dim iDelimLen As Integer
    ReDim result$(-1)

    iDelimLen = Len(delimiter$)

    start = 1
    Do
        'While Mid$(in$, start, 1) = delimiter$
        While Mid$(in$, start, iDelimLen) = delimiter$
            'start = start + 1
            start = start + iDelimLen
            If start > Len(in$) Then
                Exit Sub
            End If
        Wend
        finish = InStr(start, in$, delimiter$)
        If finish = 0 Then
            finish = Len(in$) + 1
        End If

        ReDim _Preserve result$(0 To UBound(result$) + 1)

        result$(UBound(result$)) = Mid$(in$, start, finish - start)
        start = finish + 1
    Loop While start <= Len(in$)
End Sub ' split

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' END GENERAL PURPOSE FUNCTIONS
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' BEGIN COLOR FUNCTIONS
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Function cRed~& ()
    cRed = _RGB32(255, 0, 0)
End Function

Function cOrangeRed~& ()
    cOrangeRed = _RGB32(255, 69, 0)
End Function ' cOrangeRed~&

Function cDarkOrange~& ()
    cDarkOrange = _RGB32(255, 140, 0)
End Function ' cDarkOrange~&

Function cOrange~& ()
    cOrange = _RGB32(255, 165, 0)
End Function ' cOrange~&

Function cGold~& ()
    cGold = _RGB32(255, 215, 0)
End Function ' cGold~&

Function cYellow~& ()
    cYellow = _RGB32(255, 255, 0)
End Function ' cYellow~&

' LONG-HAIRED FRIENDS OF JESUS OR NOT,
' THIS IS NOT YELLOW ENOUGH (TOO CLOSE TO LIME)
' TO USE FOR OUR COMPLEX RAINBOW SEQUENCE:
Function cChartreuse~& ()
    cChartreuse = _RGB32(127, 255, 0)
End Function ' cChartreuse~&

' WE SUBSTITUTE THIS CSS3 COLOR FOR INBETWEEN LIME AND YELLOW:
Function cOliveDrab1~& ()
    cOliveDrab1 = _RGB32(192, 255, 62)
End Function ' cOliveDrab1~&

Function cLime~& ()
    cLime = _RGB32(0, 255, 0)
End Function ' cLime~&

Function cMediumSpringGreen~& ()
    cMediumSpringGreen = _RGB32(0, 250, 154)
End Function ' cMediumSpringGreen~&

' ADDED THIS FOR THE GAUGE COLOR:
Function cSpringGreen~& ()
    cSpringGreen = _RGB32(0, 255, 160)
End Function ' cSpringGreen~&

Function cCyan~& ()
    cCyan = _RGB32(0, 255, 255)
End Function ' cCyan~&

Function cDeepSkyBlue~& ()
    cDeepSkyBlue = _RGB32(0, 191, 255)
End Function ' cDeepSkyBlue~&

Function cDodgerBlue~& ()
    cDodgerBlue = _RGB32(30, 144, 255)
End Function ' cDodgerBlue~&

Function cSeaBlue~& ()
    cSeaBlue = _RGB32(0, 64, 255)
End Function ' cSeaBlue~&

Function cBlue~& ()
    cBlue = _RGB32(0, 0, 255)
End Function ' cBlue~&

Function cBluePurple~& ()
    cBluePurple = _RGB32(64, 0, 255)
End Function ' cBluePurple~&

Function cDeepPurple~& ()
    cDeepPurple = _RGB32(96, 0, 255)
End Function ' cDeepPurple~&

Function cPurple~& ()
    cPurple = _RGB32(128, 0, 255)
End Function ' cPurple~&

Function cPurpleRed~& ()
    cPurpleRed = _RGB32(128, 0, 192)
End Function ' cPurpleRed~&

Function cDarkRed~& ()
    cDarkRed = _RGB32(160, 0, 64)
End Function ' cDarkRed~&

Function cBrickRed~& ()
    cBrickRed = _RGB32(192, 0, 32)
End Function ' cBrickRed~&

Function cDarkGreen~& ()
    cDarkGreen = _RGB32(0, 100, 0)
End Function ' cDarkGreen~&

Function cGreen~& ()
    cGreen = _RGB32(0, 128, 0)
End Function ' cGreen~&

Function cOliveDrab~& ()
    cOliveDrab = _RGB32(107, 142, 35)
End Function ' cOliveDrab~&

Function cLightPink~& ()
    cLightPink = _RGB32(255, 182, 193)
End Function ' cLightPink~&

Function cHotPink~& ()
    cHotPink = _RGB32(255, 105, 180)
End Function ' cHotPink~&

Function cDeepPink~& ()
    cDeepPink = _RGB32(255, 20, 147)
End Function ' cDeepPink~&

Function cMagenta~& ()
    cMagenta = _RGB32(255, 0, 255)
End Function ' cMagenta~&

Function cBlack~& ()
    cBlack = _RGB32(0, 0, 0)
End Function ' cBlack~&

Function cDimGray~& ()
    cDimGray = _RGB32(105, 105, 105)
End Function ' cDimGray~&

Function cGray~& ()
    cGray = _RGB32(128, 128, 128)
End Function ' cGray~&

Function cDarkGray~& ()
    cDarkGray = _RGB32(169, 169, 169)
End Function ' cDarkGray~&

Function cSilver~& ()
    cSilver = _RGB32(192, 192, 192)
End Function ' cSilver~&

Function cLightGray~& ()
    cLightGray = _RGB32(211, 211, 211)
End Function ' cLightGray~&

Function cGainsboro~& ()
    cGainsboro = _RGB32(220, 220, 220)
End Function ' cGainsboro~&

Function cWhiteSmoke~& ()
    cWhiteSmoke = _RGB32(245, 245, 245)
End Function ' cWhiteSmoke~&

Function cWhite~& ()
    cWhite = _RGB32(255, 255, 255)
    'cWhite = _RGB32(254, 254, 254)
End Function ' cWhite~&

Function cDarkBrown~& ()
    cDarkBrown = _RGB32(128, 64, 0)
End Function ' cDarkBrown~&

Function cLightBrown~& ()
    cLightBrown = _RGB32(196, 96, 0)
End Function ' cLightBrown~&

Function cKhaki~& ()
    cKhaki = _RGB32(240, 230, 140)
End Function ' cKhaki~&

Function cEmpty~& ()
    'cEmpty~& = -1
    cEmpty = _RGB32(0, 0, 0, 0)
End Function ' cEmpty~&

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' END COLOR FUNCTIONS
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' ################################################################################################################################################################
' BEGIN KEYBOARD CODE FUNCTIONS
' NOTE: ALL CODES ARE FOR _BUTTON, EXCEPT:
' cF10 (_KEYDOWN)
' cAltLeft (_KEYHIT)
' cAltRight (_KEYHIT)
' cPrintScreen (_KEYHIT) <- may slow down pc?
' cPauseBreak (_KEYHIT) <- may not work?
' ################################################################################################################################################################

Function KeyCode_Escape% ()
    KeyCode_Escape% = 2
End Function

Function KeyCode_F1% ()
    KeyCode_F1% = 60
End Function

Function KeyCode_F2% ()
    KeyCode_F2% = 61
End Function

Function KeyCode_F3% ()
    KeyCode_F3% = 62
End Function

Function KeyCode_F4% ()
    KeyCode_F4% = 63
End Function

Function KeyCode_F5% ()
    KeyCode_F5% = 64
End Function

Function KeyCode_F6% ()
    KeyCode_F6% = 65
End Function

Function KeyCode_F7% ()
    KeyCode_F7% = 66
End Function

Function KeyCode_F8% ()
    KeyCode_F8% = 67
End Function

Function KeyCode_F9% ()
    KeyCode_F9% = 68
End Function

'_KEYDOWN CODE, NOT _BUTTON CODE
Function KeyCode_F10% ()
    KeyCode_F10% = 17408
End Function

Function KeyCode_F11% ()
    KeyCode_F11% = 88
End Function

Function KeyCode_F12% ()
    KeyCode_F12% = 89
End Function

'_KEYHIT CODE, NOT _BUTTON CODE
Function KeyCode_PrintScreen% ()
    KeyCode_PrintScreen% = -44
End Function

Function KeyCode_ScrollLock% ()
    KeyCode_ScrollLock% = 71
End Function

'_KEYHIT CODE, NOT _BUTTON CODE
Function KeyCode_PauseBreak% ()
    KeyCode_PauseBreak% = 31053
End Function

Function KeyCode_Tilde% ()
    KeyCode_Tilde% = 42
End Function

Function KeyCode_1% ()
    KeyCode_1% = 3
End Function

Function KeyCode_2% ()
    KeyCode_2% = 4
End Function

Function KeyCode_3% ()
    KeyCode_3% = 5
End Function

Function KeyCode_4% ()
    KeyCode_4% = 6
End Function

Function KeyCode_5% ()
    KeyCode_5% = 7
End Function

Function KeyCode_6% ()
    KeyCode_6% = 8
End Function

Function KeyCode_7% ()
    KeyCode_7% = 9
End Function

Function KeyCode_8% ()
    KeyCode_8% = 10
End Function

Function KeyCode_9% ()
    KeyCode_9% = 11
End Function

Function KeyCode_0% ()
    KeyCode_0% = 12
End Function

Function KeyCode_Minus% ()
    KeyCode_Minus% = 13
End Function

Function KeyCode_Equal% ()
    KeyCode_Equal% = 14
End Function

Function KeyCode_BkSp% ()
    KeyCode_BkSp% = 15
End Function

Function KeyCode_Ins% ()
    KeyCode_Ins% = 339
End Function

Function KeyCode_Home% ()
    KeyCode_Home% = 328
End Function

Function KeyCode_PgUp% ()
    KeyCode_PgUp% = 330
End Function

Function KeyCode_Del% ()
    KeyCode_Del% = 340
End Function

Function KeyCode_End% ()
    KeyCode_End% = 336
End Function

Function KeyCode_PgDn% ()
    KeyCode_PgDn% = 338
End Function

Function KeyCode_NumLock% ()
    KeyCode_NumLock% = 326
End Function

Function KeyCode_KeypadSlash% ()
    KeyCode_KeypadSlash% = 310
End Function

Function KeyCode_KeypadMultiply% ()
    KeyCode_KeypadMultiply% = 56
End Function

Function KeyCode_KeypadMinus% ()
    KeyCode_KeypadMinus% = 75
End Function

Function KeyCode_Keypad7Home% ()
    KeyCode_Keypad7Home% = 72
End Function

Function KeyCode_Keypad8Up% ()
    KeyCode_Keypad8Up% = 73
End Function

Function KeyCode_Keypad9PgUp% ()
    KeyCode_Keypad9PgUp% = 74
End Function

Function KeyCode_KeypadPlus% ()
    KeyCode_KeypadPlus% = 79
End Function

Function KeyCode_Keypad4Left% ()
    KeyCode_Keypad4Left% = 76
End Function

Function KeyCode_Keypad5% ()
    KeyCode_Keypad5% = 77
End Function

Function KeyCode_Keypad6Right% ()
    KeyCode_Keypad6Right% = 78
End Function

Function KeyCode_Keypad1End% ()
    KeyCode_Keypad1End% = 80
End Function

Function KeyCode_Keypad2Down% ()
    KeyCode_Keypad2Down% = 81
End Function

Function KeyCode_Keypad3PgDn% ()
    KeyCode_Keypad3PgDn% = 82
End Function

Function KeyCode_KeypadEnter% ()
    KeyCode_KeypadEnter% = 285
End Function

Function KeyCode_Keypad0Ins% ()
    KeyCode_Keypad0Ins% = 83
End Function

Function KeyCode_KeypadPeriodDel% ()
    KeyCode_KeypadPeriodDel% = 84
End Function

Function KeyCode_Tab% ()
    KeyCode_Tab% = 16
End Function

Function KeyCode_Q% ()
    KeyCode_Q% = 17
End Function

Function KeyCode_W% ()
    KeyCode_W% = 18
End Function

Function KeyCode_E% ()
    KeyCode_E% = 19
End Function

Function KeyCode_R% ()
    KeyCode_R% = 20
End Function

Function KeyCode_T% ()
    KeyCode_T% = 21
End Function

Function KeyCode_Y% ()
    KeyCode_Y% = 22
End Function

Function KeyCode_U% ()
    KeyCode_U% = 23
End Function

Function KeyCode_I% ()
    KeyCode_I% = 24
End Function

Function KeyCode_O% ()
    KeyCode_O% = 25
End Function

Function KeyCode_P% ()
    KeyCode_P% = 26
End Function

Function KeyCode_BracketLeft% ()
    KeyCode_BracketLeft% = 27
End Function

Function KeyCode_BracketRight% ()
    KeyCode_BracketRight% = 28
End Function

Function KeyCode_Backslash% ()
    KeyCode_Backslash% = 44
End Function

Function KeyCode_CapsLock% ()
    KeyCode_CapsLock% = 59
End Function

Function KeyCode_A% ()
    KeyCode_A% = 31
End Function

Function KeyCode_S% ()
    KeyCode_S% = 32
End Function

Function KeyCode_D% ()
    KeyCode_D% = 33
End Function

Function KeyCode_F% ()
    KeyCode_F% = 34
End Function

Function KeyCode_G% ()
    KeyCode_G% = 35
End Function

Function KeyCode_H% ()
    KeyCode_H% = 36
End Function

Function KeyCode_J% ()
    KeyCode_J% = 37
End Function

Function KeyCode_K% ()
    KeyCode_K% = 38
End Function

Function KeyCode_L% ()
    KeyCode_L% = 39
End Function

Function KeyCode_Semicolon% ()
    KeyCode_Semicolon% = 40
End Function

Function KeyCode_Apostrophe% ()
    KeyCode_Apostrophe% = 41
End Function

Function KeyCode_Enter% ()
    KeyCode_Enter% = 29
End Function

Function KeyCode_ShiftLeft% ()
    KeyCode_ShiftLeft% = 43
End Function

Function KeyCode_Z% ()
    KeyCode_Z% = 45
End Function

Function KeyCode_X% ()
    KeyCode_X% = 46
End Function

Function KeyCode_C% ()
    KeyCode_C% = 47
End Function

Function KeyCode_V% ()
    KeyCode_V% = 48
End Function

Function KeyCode_B% ()
    KeyCode_B% = 49
End Function

Function KeyCode_N% ()
    KeyCode_N% = 50
End Function

Function KeyCode_M% ()
    KeyCode_M% = 51
End Function

Function KeyCode_Comma% ()
    KeyCode_Comma% = 52
End Function

Function KeyCode_Period% ()
    KeyCode_Period% = 53
End Function

Function KeyCode_Slash% ()
    KeyCode_Slash% = 54
End Function

Function KeyCode_ShiftRight% ()
    KeyCode_ShiftRight% = 55
End Function

Function KeyCode_Up% ()
    KeyCode_Up% = 329
End Function

Function KeyCode_Left% ()
    KeyCode_Left% = 332
End Function

Function KeyCode_Down% ()
    KeyCode_Down% = 337
End Function

Function KeyCode_Right% ()
    KeyCode_Right% = 334
End Function

Function KeyCode_CtrlLeft% ()
    KeyCode_CtrlLeft% = 30
End Function

Function KeyCode_WinLeft% ()
    KeyCode_WinLeft% = 348
End Function

' _KEYHIT CODE NOT _BUTTON CODE
Function KeyCode_AltLeft% ()
    KeyCode_AltLeft% = -30764
End Function

Function KeyCode_Spacebar% ()
    KeyCode_Spacebar% = 58
End Function

' _KEYHIT CODE NOT _BUTTON CODE
Function KeyCode_AltRight% ()
    KeyCode_AltRight% = -30765
End Function

Function KeyCode_WinRight% ()
    KeyCode_WinRight% = 349
End Function

Function KeyCode_Menu% ()
    KeyCode_Menu% = 350
End Function

Function KeyCode_CtrlRight% ()
    KeyCode_CtrlRight% = 286
End Function

' ################################################################################################################################################################
' END KEYBOARD CODE FUNCTIONS
' ################################################################################################################################################################

'' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
'' BEGIN DEBUGGING ROUTINES #DEBUGGING
'' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
'
'Sub DebugPrint (MyString As String)
'    If m_bDebug = TRUE Then
'        '_Echo MyString
'
'        ReDim arrLines(-1) As String
'        Dim iLoop As Integer
'        split MyString, Chr$(13), arrLines()
'        For iLoop = LBound(arrLines) To UBound(arrLines)
'            _Echo arrLines(iLoop)
'        Next iLoop
'    End If
'End Sub ' DebugPrint
'
'' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
'' END DEBUGGING ROUTINES @DEBUGGING
'' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' ################################################################################################################################################################
' #REFERENCE

' =============================================================================
' SOME USEFUL STUFF FOR REFERENCE:

' Type Name               Type suffix symbol   Minimum value                  Maximum value                Size in Bytes
' ---------------------   ------------------   ----------------------------   --------------------------   -------------
' _BIT                    `                    -1                             0                            1/8
' _BIT * n                `n                   -128                           127                          n/8
' _UNSIGNED _BIT          ~`                   0                              1                            1/8
' _BYTE                   %%                   -128                           127                          1
' _UNSIGNED _BYTE         ~%%                  0                              255                          1
' INTEGER                 %                    -32,768                        32,767                       2
' _UNSIGNED INTEGER       ~%                   0                              65,535                       2
' LONG                    &                    -2,147,483,648                 2,147,483,647                4
' _UNSIGNED LONG          ~&                   0                              4,294,967,295                4
' _INTEGER64              &&                   -9,223,372,036,854,775,808     9,223,372,036,854,775,807    8
' _UNSIGNED _INTEGER64    ~&&                  0                              18,446,744,073,709,551,615   8
' SINGLE                  ! or none            -2.802597E-45                  +3.402823E+38                4
' DOUBLE                  #                    -4.490656458412465E-324        +1.797693134862310E+308      8
' _FLOAT                  ##                   -1.18E-4932                    +1.18E+4932                  32(10 used)
' _OFFSET                 %&                   -9,223,372,036,854,775,808     9,223,372,036,854,775,807    Use LEN
' _UNSIGNED _OFFSET       ~%&                  0                              18,446,744,073,709,551,615   Use LEN
' _MEM                    none                 combined memory variable type  N/A                          Use LEN

' div: int1% = num1% \ den1%
' mod: rem1% = num1% MOD den1%

'#END

Print this item

  Odysee channel for QB64pe
Posted by: SpriggsySpriggs - 11-01-2022, 06:25 PM - Forum: General Discussion - Replies (18)

I've created an Odysee channel for the QB64pe project! Odysee is a blockchain site for sharing videos, livestreams, and articles. The first article I've posted there is the announcement of version 3.4.0! To see the article, follow this link: https://odysee.com/v3.4.0

I hope to also share some videos there later for showcasing new features as they come out. Maybe this could be something like Fellippe was trying to get going a while back. I don't know. I'm not as charismatic as he is. Big Grin

If you all have ideas for videos and things, please let me know. I can't guarantee I'll be quick to get stuff out, though.

Print this item

  QB64 Phoenix Edition v3.4.0 Released!
Posted by: DSMan195276 - 11-01-2022, 05:46 PM - Forum: Announcements - Replies (38)

QB64 Phoenix Edition v3.4.0!
https://github.com/QB64-Phoenix-Edition/...tag/v3.4.0

Enhancements

  • #189, #216, #225, #227, #230 - Added several cross-platform dialogs - @a740g, @mkilgore
    • The commands make a best-effort of determining the way to show the dialog based on what is provided by the system.
    • The follow new commands were added:

Bug Fixes
  • #218, #222 -
    setup_osx.command
    now works correctly when run from any directory - @mkilgore

Full Changelog: https://github.com/QB64-Phoenix-Edition/...0...v3.4.0

Print this item