Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
multiplayer spacewar
#1
Seeing @NakedApe's Rock Jockey 2.0 game has inspired me to dig out the Spacewar! game I was working on but stalled on a couple years back, and see if I can finish it. 

The part I got bogged down on was a vector polygon shape editor to let players edit what their ship looks like using the mouse, where they click on a grid to add / remove / change start/end points for various line segments. It kinda sorta works but I have to finish the code that keeps track of whether they're adding/deleting/changing an anchor point. I might still have to finish the part where it loads/saves the shape definitions from/to a file.

Anyway here is all the code if anyone wants to play with it. I must have introduced a bug into the game in the last version, because it crashes when the player destroys all the enemies and the round is supposed to end. I'll fix it when I get a chance, I just wanted to share what I have so folks can play around with it in the meantime.

The game "multispaceware72.bas":

Code: (Select All)
Dim Shared m_sTitle As String: m_sTitle = "Fast Zap 'Em : Multispacewar! by Softintheheadware v0.72"
_Title m_sTitle ' display in the Window's title bar

' DATE         WHO-DONE-IT   DID-WHAT
' 2001-06-10   Tassadar      created "Collisions" program in VB6
' 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
' 2022-11-06   madscijr      Version 0.57, one player can control more than one ship.
' 2022-11-24   madscijr      Version 0.69, added constants/parameters/types to be more flexible
' 2022-12-01   madscijr      Version 0.71 (last version before project stalled!)
' 2025-01-05   madscijr      Version 0.72 added line intersection routines by MasterGy and bplus, to be used later

' 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
' * 2-players control 16 ships (button #2 changes which ship)
' * fix jerky ship movement (ships should move & respond like Asteroids) = fixed by using Single instead of Integer!

' TODO:
' * fix "out of memory" error that happens when all enemies destroyed & round ends
' * get VectorEditor program working (allows player to edit what their ship looks like)
'   - uses graphics objects format borrowed from Widescreen Asteroids by Terry Ritchie
' * make sure game works with new code from "VectorEditor" program
'   - update collision checking to use IsIntersection function by MasterGy
'     and/or LineIntersectLine & TwoSegmentsIntersect by bplus
'     at https://qb64phoenix.com/forum/showthread.php?tid=2134&pid=29992
' * factor ship's heading into bullet's speed
' * move scores + player status to top or edges of screen
' * 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
' * limited fuel/ammo
' * option: fire button auto-repeat
' * define ships with polygons + improve collision detection accuracy
' * polygon editor
' (lots more plans for phase 2 and beyond)

' OTHER TODO:
' * fix DrawVectorObjectTest1 demo where program crashes on exit (press ESC)

' ORIGINAL CREDITS FROM "Collisions" VB6 game by Tassadar
' downloaded from PlanetSourceCode.com in 2001:
' ********************************************************************************
' *            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                                                       *
' ********************************************************************************

' BOOLEAN CONSTANTS
Const FALSE = 0
Const TRUE = Not FALSE

' OTHER SETTINGS
Const cFPS = 120
Const cMinStars = 50
Const cMaxStars = 150
Const cSpeed = 2


Const MIN_PLAYERS = 1
Const MAX_PLAYERS = 16

Const MAX_ROUNDS = 255 ' HOW MANY ROUNDS DOES THE GAME LAST?

Const TURN_SPEED = 36 ' 72=normal, 36=faster, 18=superfast
Const SHIP_ACCEL = 0.1 ' how fast does it accelerate: 0.025, 0.1, 0.05
Const MAX_SPEED = 12 ' what is the ships max speed: 6, 12

Const SHIP_RADIUS = 10 ' What is the radius of the players ship
Const BULLET_RADIUS = 2 ' What is the radius of the bullets

Const SHOOT_DELAY = 200 ' Delay between shots for the ship
Const BULLET_SPEED = 9 ' Bullet speed - Ship speed + bullet speed = overall bulletspeed: 6, 9
Const BULLET_LIFESPAN = 1 ' # seconds bullet is alive
Const BULLETS_STOP_BULLETS = TRUE

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 WRAP_BULLET = TRUE

Const NUM_SHIELDS = 2 ' 100
Const MAX_SHIELDS = 10
Const BONUS_SHIELDS = 2 ' 50

Const BULLET_DAMAGE = 1

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

Type ShipType
    PlayerNum As Integer

    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 Single 'Integer ' X co-ordinate of the ship
    yPos As Single '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_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
    Button1_IsPressed As Integer ' Is Button #1 depressed
    Button2_IsPressed As Integer ' Is Button #2 depressed

    BodyColor As _Unsigned Long
    EngineColor As _Unsigned Long
    FlameColor As _Unsigned Long
End Type ' ShipType

Type PlayerType
    WhichShip As Integer ' stores what is being controlled (1-16 = ship #, 0 = all)
    FirstShip As Integer
    LastShip As Integer
   
    'Button2_IsPressed As Integer ' Is Button #2 depressed
    Button2_IsReady As Integer ' TRACKS WHETHER BUTTON HAS BEEN RELEASED SINCE LAST PRESSED (TRUE/FALSE)
   
    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
    Button1_KeyCode As Integer ' stores key code for Button #1 (shoot)
    Button2_KeyCode As Integer ' stores key code for Button #2 (option)
   
    instructMoveUp As String
    instructMoveDown As String
    instructMoveLeft As String
    instructMoveRight As String
    instructButton1 As String
    instructButton2 As String
    'instructButton3 As String
    'instructButton4 As String
End Type ' PlayerType

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

Type BulletType
    xPos As Single 'Integer ' X co-ordinate of this bullet
    yPos As Single '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
    z As Integer ' zorder in which object is stacked (in front of or behind other objects)
    FillColor As _Unsigned Long ' color to fill in object with (use cEmpty for transparent)
    PreviewColor As _Unsigned Long '
End Type ' ObjectType

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

' HOLDS AN X,Y COORDINATE
Type CoordType
    x As Integer
    y 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
' ----------------------------------------------------------------------------------------------------------------------------------------------------------------

' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
' BEGIN CHEAPO PLANETARIUM TYPES
' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
' HOLDS LOCATION + COLOR OF EACH STAR
Type StarType
    x As Integer
    y As Integer
   
    ColorIndex As _Unsigned Long ' the star's current color
    TwinkleCounter As Integer ' counter for twinkles
    MaxTwinkCount As Integer ' controls how fast the star twinkles
   
    width As Integer
    MinWidth As Integer ' smallest width
    MaxWidth As Integer ' largest width
    WidthCounter As Integer ' counter for width
    MaxWidthCount As Integer ' controls how fast the star size fluctuates
    BigCounter As Integer ' counter for max width
    MaxBigCount As Integer ' controls how long the star stays big
End Type ' StarType
' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
' END CHEAPO PLANETARIUM 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$ = "0.57"

' GAME CONTROLLER MAPPING
ReDim Shared m_arrButtonCode(1 To 99) As Integer
ReDim Shared m_arrButtonKey(1 To 99) As String
ReDim Shared m_arrButtonKeyDesc(0 To 512) As String
Dim Shared m_bInitialized As Integer: m_bInitialized = FALSE

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

' DISPLAY MODE
Dim Shared m_bCocktailMode As Integer: m_bCocktailMode = TRUE

' 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_iNumShips As Integer ' How many ships
Dim Shared m_iShipsPerPlayer As Integer ' How many ships does each player control
Dim Shared m_bSimpleEnemies As Integer ' enable/disable enemy type #1
Dim Shared m_iRoundsPerGame As Integer ' how many rounds per game (0 = infinite)

Dim Shared m_bGameOver As Integer ' Is the game over
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_arrPlayer(-1) As PlayerType ' A nice array of players
ReDim Shared m_arrShip(-1) As ShipType ' A nice array of ships
ReDim Shared m_arrBullet(-1) As BulletType ' A nice array of bullets
ReDim Shared m_arrEnemy(-1) As EnemyType ' A nice array of enemies

' OBJECT VARIABLES
ReDim Shared m_arrObject(1 To 32) As ObjectType ' m_arrObject(ObjectIndex)
ReDim Shared m_arrOrder(1 To 32) As Integer ' TBD
ReDim Shared m_arrLines(1 To 32, 1 To 32) As LineType ' m_arrLines(ObjectIndex, LineIndex)
ReDim Shared m_arrLineStyle(1 To 8) As Long
ReDim Shared m_arrStars(1 To cMaxStars) As StarType
ReDim Shared m_arrGrayColor(-1) As _Unsigned Long
ReDim Shared m_arrEditGrid(0 To 126, 0 To 126) As CoordType ' CONVERTS EDITOR GRID TO POLAR COORDINATES

' ****************************************************************************************************************************************************************
' 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 InitializeMenu
    ' *****************************************************************************
    ' *** BEGIN ADD YOUR MENU CHOICES HERE ****************************************

    AddNextMenuItem "Start game", ""
   
    AddNextMenuItem "Game options", "Change # players, ships, etc."
   
    AddNextMenuItem "DrawVectorObjectTest1", "Test simple vector graphics engine."
   
   
    '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 ' InitializeMenu

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

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$ = "Start game" Then game: ClearKeyboard 3
   
    ElseIf in$ = "Game options" Then OptionsMenu
   
    ElseIf in$ = "DrawVectorObjectTest1" Then DrawVectorObjectTest1: 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 InitializeMenu1
    ' *****************************************************************************
    ' *** 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 ' InitializeMenu

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

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 iLabelColor~&: iLabelColor~& = cGold
    Dim iValueColor~&: iValueColor~& = cOrange
   
    Dim iInstructColor~&: iInstructColor~& = cCyan
    Dim iMenuColor~&: iMenuColor~& = cWhite
    Dim iRunColor~&: iRunColor~& = cYellow
    Dim iInfoColor~&: iInfoColor~& = cSilver
   
    Dim iMaxColumns As Integer
    Dim iIndex As Integer
   
    Dim bValue 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 OPTIONS
    m_iPlayers = 2
    m_iShipsPerPlayer = 3
    m_iNumShips = 1
   
    ' INITIALIZE MENU
    Cls
    Print "Initializing..."
    InitializeMenu
   
    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
    iStartRow = 14
    iInfoRow = iMenuSize + 10
    iInfoSize = 10

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

            ' SHOW INSTRUCTIONS
            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"

            ' SHOW OPTIONS
            Color iLabelColor~&, iBackColor~&
            PrintString 8, 0, "Players         :"
            PrintString 9, 0, "Ships per player:"
            PrintString 10, 0, "Rounds per game :"
            PrintString 11, 0, "Simple enemies  :"
           
            Color iValueColor~&, iBackColor~&
            PrintString 8, 18, cstr$(m_iPlayers) + " "
            PrintString 9, 18, cstr$(m_iShipsPerPlayer) + " "
            bValue = IIF(m_iRoundsPerGame = 0, TRUE, FALSE)
            PrintString 10, 18, IIFSTR$(bValue, "Infinite", cstr$(m_iRoundsPerGame))
            PrintString 11, 18, IIFSTR$(m_bSimpleEnemies, "Yes", "No ")
           
            ' CLEAR KEYBOARD BUFFER
            ClearKeyboard 1
           
            ' INITIALIZE DONE
            bInitPage = FALSE
        End If
       
        ' (RE)DRAW MENU
        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
                    ' 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 BOX DRAWING ROUTINES
' ################################################################################################################################################################

' /////////////////////////////////////////////////////////////////////////////
' DRAW A 2-D BOX (OUTLINE)
' https://www.qb64.org/wiki/LINE

Sub DrawBoxOutline (iX As Integer, iY As Integer, iSize As Integer, fgColor As _Unsigned Long)
    Line (iX, iY)-(iX + (iSize - 1), iY + (iSize - 1)), fgColor, B ' Draw box outline
End Sub ' DrawBoxOutline

' /////////////////////////////////////////////////////////////////////////////
' DRAW A 2-D BOX (SOLID)
' https://www.qb64.org/wiki/LINE

' Renamed DrawBox/DrawBoxLine to DrawSolidBox

Sub DrawBoxSolid (iX As Integer, iY As Integer, iSize As Integer, fgColor As _Unsigned Long)
    Line (iX, iY)-(iX + (iSize - 1), iY + (iSize - 1)), fgColor, BF ' Draw a solid box
End Sub ' DrawBoxSolid

' /////////////////////////////////////////////////////////////////////////////
' DRAW A 2-D RECTANGLE

'DrawRect 0, iX, iY, iSizeW, iSizeH, fgColor, bgColor

Sub DrawRect (img&, iX As Integer, iY As Integer, iSizeW As Integer, iSizeH As Integer, fgColor As _Unsigned Long, bgColor As _Unsigned Long)
    'If img& < -1 Then
    If img& <= 0 Then
        ' Select target image
        _Dest img& ': Cls , cEmpty
       
        ' Draw fill (bgColor)
        If bgColor <> cEmpty Then
            Line (iX, iY)-(iX + (iSizeW - 1), iY + (iSizeH - 1)), bgColor, BF ' Draw a solid rectangle
        End If
       
        ' Draw outline (fgColor)
        If fgColor <> cEmpty Then
            Line (iX, iY)-(iX + (iSizeW - 1), iY + (iSizeH - 1)), fgColor, B ' Draw rectangle outline
        End If
    End If
End Sub ' DrawRect

' /////////////////////////////////////////////////////////////////////////////
' 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

'DrawCircle 0, iX, iY, iRadius, iThickness, fgColor, bgColor
Sub DrawCircle (img&, iX As Integer, iY As Integer, iRadius As Integer, iThickness As Integer, fgColor As _Unsigned Long, bgColor As _Unsigned Long)
    Dim iLoop As Integer
    Dim iNextRadius As Integer
    Dim iRadiusError As Integer
    Dim iNextX As Integer
    Dim iNextY As Integer
    If (img& <= 0) And (iThickness > 0) And (fgColor <> cEmpty) Then
        ' Select target image
        _Dest img& ': Cls , cEmpty
       
        ' Draw circle fill
        If bgColor <> cEmpty Then
            iNextRadius = Abs(iRadius)
            iRadiusError = -iNextRadius
            iNextX = iNextRadius
            iNextY = 0
            If iNextRadius = 0 Then
                PSet (iX, iY), bgColor
            Else
                ' 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 (iX - iNextX, iY)-(iX + iNextX, iY), bgColor, BF
                While iNextX > iNextY
                    iRadiusError = iRadiusError + iNextY * 2 + 1
                    If iRadiusError >= 0 Then
                        If iNextX <> iNextY + 1 Then
                            Line (iX - iNextY, iY - iNextX)-(iX + iNextY, iY - iNextX), bgColor, BF
                            Line (iX - iNextY, iY + iNextX)-(iX + iNextY, iY + iNextX), bgColor, BF
                        End If
                        iNextX = iNextX - 1
                        iRadiusError = iRadiusError - iNextX * 2
                    End If
                    iNextY = iNextY + 1
                    Line (iX - iNextX, iY - iNextY)-(iX + iNextX, iY - iNextY), bgColor, BF
                    Line (iX - iNextX, iY + iNextY)-(iX + iNextX, iY + iNextY), bgColor, BF
                Wend
            End If
        End If
       
        ' Draw circle outline
        If fgColor <> cEmpty Then
            If iRadius = 0 Then
                PSet (iX, iY), fgColor
            Else
                iNextRadius = iRadius
                For iLoop = 1 To iThickness
                   
                    ' DRAW CIRCLE
                    ' CIRCLE (x, y), radius, color
                    'CIRCLE [[[STEP]]](column, row), radius%, [drawColor%][, startRadian!, stopRadian!] [, aspect!]
                    Circle (iX, iY), iNextRadius, fgColor
                   
                    'Line (iLoop * 8, iHeight * 16)-(iLoop * 8 + 8, _Height), cGray, BF
                    'Circle (iX, iY), 4, cRed
                   
                    iNextRadius = iNextRadius - 1
                    If iNextRadius = 0 Then
                        PSet (iX, iY), fgColor
                        Exit For
                    End If
                Next iLoop
            End If
        End If
       
    End If
End Sub ' DrawCircle

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

'DrawCircleOutline 0, iX, iY, iRadius, iThickness, fgColor
Sub DrawCircleOutline (img&, iX As Integer, iY As Integer, iRadius As Integer, iThickness As Integer, fgColor As _Unsigned Long)
    Dim iNextRadius As Integer
    Dim iLoop As Integer
    If (img& <= 0) And (iThickness > 0) And (fgColor <> cEmpty) Then
        ' Select target image
        _Dest img& ': Cls , cEmpty
       
        ' Initialize
        iNextRadius = iRadius
       
        ' Draw circle
        If Radius = 0 Then
            PSet (iX, iY), fgColor
        Else
            For iLoop = 1 To iThickness
               
                ' DRAW CIRCLE
                ' CIRCLE (x, y), radius, color
                'CIRCLE [[[STEP]]](column, row), radius%, [drawColor%][, startRadian!, stopRadian!] [, aspect!]
                Circle (iX, iY), iNextRadius, fgColor
               
                'Line (iLoop * 8, iHeight * 16)-(iLoop * 8 + 8, _Height), cGray, BF
                'Circle (iX, iY), 4, cRed
               
                iNextRadius = iNextRadius - 1
                If iNextRadius = 0 Then
                    PSet (iX, iY), fgColor
                    Exit For
                End If
            Next iLoop
        End If
    End If
End Sub ' DrawCircleOutline

' /////////////////////////////////////////////////////////////////////////////
' DRAW A 2-D RECTANGLE (OUTLINE)

'DrawRectOutline iX, iY, iSizeW, iSizeH, fgColor

Sub DrawRectOutline (iX As Integer, iY As Integer, iSizeW As Integer, iSizeH As Integer, fgColor As _Unsigned Long)
    Line (iX, iY)-(iX + (iSizeW - 1), iY + (iSizeH - 1)), fgColor, B ' Draw rectangle outline
End Sub ' DrawRectOutline

' /////////////////////////////////////////////////////////////////////////////
' DRAW A 2-D RECTANGLE (SOLID)

'DrawRectSolid iX, iY, iSizeW, iSizeH, fgColor

Sub DrawRectSolid (iX As Integer, iY As Integer, iSizeW As Integer, iSizeH As Integer, fgColor As _Unsigned Long)
    Line (iX, iY)-(iX + (iSizeW - 1), iY + (iSizeH - 1)), fgColor, BF ' Draw a solid rectangle
End Sub ' DrawRectSolid

' /////////////////////////////////////////////////////////////////////////////
' DRAW A 2-D BOX (OUTLINE)
' https://www.qb64.org/wiki/LINE

' The style parameter 0-255 doesn't seem to have a solid line?
' For that, use DrawOutlineBox.

' LINE [STEP] [(column1, row1)]-[STEP] (column2, row2), color[, [{B|BF}], style%]
' B creates a box outline with each side parallel to the program screen sides. BF creates a filled box.
' The style% signed INTEGER value sets a dotted pattern to draw the line or rectangle outline.

Sub DrawStyledOutlineBox (iX%, iY%, iSize%, iColor~&, iStyle%)
    Line (iX%, iY%)-(iX% + (iSize% - 1), iY% + (iSize% - 1)), iColor~&, B , iStyle%
End Sub ' DrawStyledOutlineBox

' /////////////////////////////////////////////////////////////////////////////
' DRAW A 2-D BOX (OUTLINE) WITH A SOLID LINE

Sub DrawOutlineBox (iX%, iY%, iSize2%, iColor~&, iWeight2%)
    Dim iFromX%
    Dim iFromY%
    Dim iToX%
    Dim iToY%
    iSize% = iSize2% - 1
    iWeight% = iWeight2% - 1
    If iWeight% = 0 Then
        ' TOP LINE
        iFromX% = iX%
        iFromY% = iY%
        iToX% = iX% + iSize%
        iToY% = iY%
        Line (iFromX%, iFromY%)-(iToX%, iToY%), iColor~&, BF

        ' BOTTOM LINE
        iFromX% = iX%
        iFromY% = iY% + iSize%
        iToX% = iX% + iSize%
        iToY% = iY% + iSize%
        Line (iFromX%, iFromY%)-(iToX%, iToY%), iColor~&, BF

        ' LEFT LINE
        iFromX% = iX%
        iFromY% = iY%
        iToX% = iX%
        iToY% = iY% + iSize%
        Line (iFromX%, iFromY%)-(iToX%, iToY%), iColor~&, BF

        ' RIGHT LINE
        iFromX% = iX% + iSize%
        iFromY% = iY%
        iToX% = iX% + iSize%
        iToY% = iY% + iSize%
        Line (iFromX%, iFromY%)-(iToX%, iToY%), iColor~&, BF
    ElseIf iWeight% > 0 Then
        ' TOP LINE
        For iFromY% = iY% To (iY% + iWeight%)
            iFromX% = iX%
            'iFromY% = iY%
            iToX% = iX% + iSize%
            iToY% = iFromY%
            Line (iFromX%, iFromY%)-(iToX%, iToY%), iColor~&, BF
        Next iFromY%

        ' BOTTOM LINE
        For iFromY% = ((iY% + iSize%) - iWeight%) To (iY% + iSize%)
            iFromX% = iX%
            'iFromY% = iY% + iSize%
            iToX% = iX% + iSize%
            iToY% = iFromY%
            Line (iFromX%, iFromY%)-(iToX%, iToY%), iColor~&, BF
        Next iFromY%

        ' LEFT LINE
        For iFromX% = iX% To (iX% + iWeight%)
            'iFromX% = iX%
            iFromY% = iY%
            iToX% = iFromX%
            iToY% = iY% + iSize%
            Line (iFromX%, iFromY%)-(iToX%, iToY%), iColor~&, BF
        Next iFromX%

        ' RIGHT LINE
        For iFromX% = ((iX% + iSize%) - iWeight%) To (iX% + iSize%)
            'iFromX% = iX% + iSize%
            iFromY% = iY%
            iToX% = iFromX%
            iToY% = iY% + iSize%
            Line (iFromX%, iFromY%)-(iToX%, iToY%), iColor~&, BF
        Next iFromX%
    End If
End Sub ' DrawOutlineBox

' /////////////////////////////////////////////////////////////////////////////
'DrawSquare 0, x1, y1, size, fgcolor, bgcolor

Sub DrawSquare (img&, x1%, y1%, size%, fgcolor~&, bgcolor~&)
    Dim x2%, y2%
    If img& < -1 Then
        _Dest img& ': Cls , cEmpty

        x2% = (x1% + size%) - 1
        y2% = (y1% + size%) - 1

        Line (x1%, y1%)-(x2%, y1%), fgcolor~&, , 65535
        Line (x2%, y1%)-(x2%, y2%), fgcolor~&, , 65535
        Line (x2%, y2%)-(x1%, y2%), fgcolor~&, , 65535
        Line (x1%, y2%)-(x1%, y1%), fgcolor~&, , 65535

        If bgcolor~& <> cEmpty Then
            'PAINT [STEP] (column%, row%), fillColor[, borderColor%]
            Paint (x1% + 1, y1% + 1), bgcolor~&, fgcolor~&
        End If
    End If
End Sub ' Draw Square

' ################################################################################################################################################################
' END BOX DRAWING ROUTINES
' ################################################################################################################################################################

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

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

Sub DrawVectorObjectTest1
    Dim RoutineName As String: RoutineName = "DrawVectorObjectTest1"
    Dim iFPS As Integer: iFPS = cFPS
    Dim iLoop As Integer
    Dim iObject As Integer
    Dim iLayer As Integer
    Dim iLine As Integer
    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 iStyleCountMax As Integer: iStyleCountMax = iFPS \ 12 ' change 4x a second
    Dim iStyleCountNext As Integer: iStyleCountNext = 0
    Dim iLineStyleIndex As Integer
    Dim lngLineStyle ' line style
    Dim lngDashedLineStyle ' line style for other objects
    Dim lngSolidLineStyle ' selected object's line style
    Dim iNumStars As Integer
    Dim iValue As Integer
    Dim iMinValue As Integer
    Dim iMaxValue As Integer
    Dim iStarLoop As Integer
    Dim x1%
    Dim x2%
    Dim y1%
    Dim y2%
    Dim imgStars& ' used for drawing background
    Dim imgText& ' used for drawing text
    Dim imgObjects& ' used for drawing objects
    Dim imgTemp& ' temporary drawing area
   
    ' =============================================================================
    ' INITIALIZE
    InitializeRandom
    Screen _NewImage(iMaxX, iMaxY, 32) ' 100 text columns x 40 text rows
   
    ' USE LATER FOR DRAWING LAYERS:
    imgStars& = _NewImage(iMaxX, iMaxY, 32) ' background stars
    imgText& = _NewImage(iMaxX, iMaxY, 32) ' text
    imgObjects& = _NewImage(iMaxX, iMaxY, 32) ' frontground objects
    imgTemp& = _NewImage(iMaxX, iMaxY, 32) ' temporary
   
    ' =============================================================================
    ' START NEW GAME
    Do
        _KeyClear

        ' CONFIGURE PRINTING FOR _PrintString
        _PrintMode _FillBackground
        '_PrintMode _KEEPBACKGROUND

        ' INIT OBJECT DEFINITIONS
        InitVectorObjects
       
        ' INIT VARS
        sKey = ""
       
        ' PLACE OBJECTS
        iX = 0: iY = 0
        iValue = UBound(m_arrObject)
        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
            m_arrObject(iObject).z = iValue
            m_arrObject(iObject).FillColor = cBlack
           
            iX = iX + 200
            If iX > 800 Then
                iX = 0
                iY = iY + 200
                If iY > 640 Then
                    iY = 0
                End If
            End If
            iValue = iValue - 1
        Next iObject
       
        ' INIT OBJECT Z-ORDER ARRAY
        ReDim m_arrOrder(LBound(m_arrObject) To UBound(m_arrObject)) As Integer
        For iObject = LBound(m_arrObject) To UBound(m_arrObject)
            m_arrOrder(m_arrObject(iObject).z) = iObject
        Next iObject
       
        ' SAVE LINE STYLE SEQUENCE
        iLoop = 0
        iLoop = iLoop + 1: m_arrLineStyle(iLoop) = 61680 ' 1111000011110000
        iLoop = iLoop + 1: m_arrLineStyle(iLoop) = 30840 ' 0111100001111000
        iLoop = iLoop + 1: m_arrLineStyle(iLoop) = 15420 ' 0011110000111100
        iLoop = iLoop + 1: m_arrLineStyle(iLoop) = 7710 ' 0001111000011110
        iLoop = iLoop + 1: m_arrLineStyle(iLoop) = 3855 ' 0000111100001111
        iLoop = iLoop + 1: m_arrLineStyle(iLoop) = 34695 ' 1000011110000111
        iLoop = iLoop + 1: m_arrLineStyle(iLoop) = 50115 ' 1100001111000011
        iLoop = iLoop + 1: m_arrLineStyle(iLoop) = 57825 ' 1110000111100001
        iLineStyleIndex = LBound(m_arrLineStyle)
        lngDashedLineStyle = m_arrLineStyle(iLineStyleIndex)
        lngSolidLineStyle = 65535
       
        ' SAVE COLORS FOR EACH OBJECT (FROM FIRST LINE SEGMENT) TO ARRAY FOR TEXT
        For iObject = LBound(m_arrObject) To UBound(m_arrObject)
            iLine = LBound(m_arrLines, 2)
            m_arrObject(iObject).PreviewColor = m_arrLines(iObject, iLine).color
        Next iObject
       
        ' SAVE COLORS FOR STARS AND GRAYSCALE OBJECTS
        AddGrayscaleColors m_arrGrayColor()
       
        ' -----------------------------------------------------------------------------
        ' PLACE STARS RANDOMLY
        iNumStars = RandomNumber%(cMinStars, cMaxStars)
        ReDim m_arrStars(1 To iNumStars) As StarType
        For iLoop = 1 To iNumStars
            m_arrStars(iLoop).x = RandomNumber%(iMinX, iMaxX)
            m_arrStars(iLoop).y = RandomNumber%(iMinY, iMaxY)
            m_arrStars(iLoop).ColorIndex = RandomNumber%(LBound(m_arrGrayColor), UBound(m_arrGrayColor))
           
            ' Assign a width 1-3 (with different probability for each)
            iValue = RandomNumber%(1, 100)
            If iValue > 98 Then
                m_arrStars(iLoop).MinWidth = RandomNumber%(2, 3)
                m_arrStars(iLoop).MaxWidth = 3
            ElseIf iValue > 85 Then
                m_arrStars(iLoop).MinWidth = RandomNumber%(1, 2)
                m_arrStars(iLoop).MaxWidth = 2
            Else
                m_arrStars(iLoop).MinWidth = RandomNumber%(0, 1)
                m_arrStars(iLoop).MaxWidth = 1
            End If
            ' Set initial width to normal (MaxWidth)
            m_arrStars(iLoop).width = m_arrStars(iLoop).MaxWidth
           
            ' Determine how quickly size changes
            ' Anywhere between 1/30 second and 1 seconds
            iMinValue = iFPS \ 30
            iMaxValue = iFPS
            m_arrStars(iLoop).MaxWidthCount = RandomNumber%(iMinValue, iMaxValue)
            m_arrStars(iLoop).WidthCounter = 0
           
            ' Determine how long size is changed
            ' Anywhere between 1/100 second and 1/50 seconds
            iMinValue = iFPS \ 100
            iMaxValue = iFPS \ 50
            m_arrStars(iLoop).MaxBigCount = RandomNumber%(iMinValue, iMaxValue)
            m_arrStars(iLoop).BigCounter = 0
           
            ' Determine how quickly they twinkle
            ' Anywhere between 1/120 second and 1/20 seconds
            iMinValue = iFPS \ 120
            iMaxValue = iFPS \ 20
            m_arrStars(iLoop).MaxTwinkCount = RandomNumber%(iMinValue, iMaxValue)
            m_arrStars(iLoop).TwinkleCounter = 0
        Next iLoop
       
        ' ================================================================================================================================================================
        ' BEGIN MAIN LOOP
        While TRUE = TRUE
           
            ' CLEAR OBJECTS LAYER
            _Dest imgObjects&: Cls , cEmpty
           
            ' MOVE AND ADD ENABLED OBJECTS (IN STACKING ORDER)
            For iLayer = UBound(m_arrOrder) To LBound(m_arrOrder) Step -1
                ' Get next object
                iObject = m_arrOrder(iLayer)
               
                ' 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 - cSpeed
                            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 + cSpeed
                            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 - cSpeed
                            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 + cSpeed
                            If m_arrObject(iObject).y > iMaxY Then
                                m_arrObject(iObject).y = iMinY
                            End If
                        End If
                    End If
                   
                    ' Clear temporary (layer and draw on it
                    _Dest imgTemp&: Cls , cEmpty
                   
                    ' Draw object's line segments
                    For iLine = LBound(m_arrLines, 2) To UBound(m_arrLines, 2)
                        If m_arrLines(iObject, iLine).IsEnabled = TRUE Then
                            If m_arrLines(iObject, iLine).IsLast = FALSE Then
                                'Line (x1%, y1%)-(x2%, y2%), arrColor(arrStars(iLoop).ColorIndex), BF, lngLineStyle
                                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, , lngSolidLineStyle
                            Else
                                Exit For
                            End If
                        End If
                    Next iLine
                   
                    ' Draw fill color if not transparent
                    If m_arrObject(iObject).FillColor <> cEmpty Then
                        ' Fill in current object with its fill color...
                        'PAINT [STEP] (column%, row%), fillColor[, borderColor%]
                        Paint (m_arrObject(iObject).x, m_arrObject(iObject).y), _
                            m_arrObject(iObject).FillColor, m_arrObject(iObject).PreviewColor
                    End If
                   
                    ' Make other objects appear drawn with a dashed line
                    If iObject <> iWhich Then
                        ' Outline with a dashed line
                        For iLine = LBound(m_arrLines, 2) To UBound(m_arrLines, 2)
                            If m_arrLines(iObject, iLine).IsEnabled = TRUE Then
                                If m_arrLines(iObject, iLine).IsLast = FALSE Then
                                    'Line (x1%, y1%)-(x2%, y2%), arrColor(arrStars(iLoop).ColorIndex), BF, lngLineStyle
                                    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) _
                                        , _
                                        cBlack, , lngDashedLineStyle
                                Else
                                    Exit For
                                End If
                            End If
                        Next iLine
                    End If
                   
                    ' Add new object to objects layer
                    '_PUTIMAGE , sourceHandle&, destHandle& 'size full source to fit full destination area
                    _PutImage , imgTemp&, imgObjects&
                   
                End If
            Next iLayer
           
            ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
            ' BEGIN STARS
           
            ' Twinkle twinkle little stars
            _Dest imgStars&: Cls , cEmpty
            For iStarLoop = LBound(m_arrStars) To UBound(m_arrStars)
                ' increment twinkle counter
                m_arrStars(iStarLoop).TwinkleCounter = m_arrStars(iStarLoop).TwinkleCounter + 1
               
                ' is it time to twinkle the color?
                If m_arrStars(iStarLoop).TwinkleCounter > m_arrStars(iStarLoop).MaxTwinkCount Then
                    m_arrStars(iStarLoop).TwinkleCounter = 0 ' reset counter
                    m_arrStars(iStarLoop).ColorIndex = m_arrStars(iStarLoop).ColorIndex + 1 ' increment color
                    If m_arrStars(iStarLoop).ColorIndex > UBound(m_arrGrayColor) Then
                        m_arrStars(iStarLoop).ColorIndex = LBound(m_arrGrayColor) ' reset color
                    End If
                End If
               
                ' increment width counter
                If m_arrStars(iStarLoop).BigCounter = 0 Then
                    m_arrStars(iStarLoop).WidthCounter = m_arrStars(iStarLoop).WidthCounter + 1
                   
                    ' is it time to fluctuate the width
                    If m_arrStars(iStarLoop).WidthCounter > m_arrStars(iStarLoop).MaxWidthCount Then
                        m_arrStars(iStarLoop).WidthCounter = 0 ' reset counter
                        m_arrStars(iStarLoop).BigCounter = 1 ' start big counter
                        m_arrStars(iStarLoop).width = m_arrStars(iStarLoop).MinWidth ' twinkle width
                    Else
                        m_arrStars(iStarLoop).width = m_arrStars(iStarLoop).MaxWidth ' normal width
                    End If
                Else
                    ' increment big counter
                    m_arrStars(iStarLoop).BigCounter = m_arrStars(iStarLoop).BigCounter + 1
                   
                    ' is it time to return to normal size?
                    If m_arrStars(iStarLoop).BigCounter > m_arrStars(iStarLoop).MaxBigCount Then
                        m_arrStars(iStarLoop).BigCounter = 0 ' reset counter
                        m_arrStars(iStarLoop).width = m_arrStars(iStarLoop).MaxWidth ' normal width
                    End If
                End If
               
                ' get size
                x1% = m_arrStars(iStarLoop).x: x2% = x1% + m_arrStars(iStarLoop).width
                y1% = m_arrStars(iStarLoop).y: y2% = y1% + m_arrStars(iStarLoop).width
               
                ' (re)draw it
                Line (x1%, y1%)-(x2%, y2%), m_arrGrayColor(m_arrStars(iStarLoop).ColorIndex), BF
            Next iStarLoop
            ' END STARS
            ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
           
            ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
            ' SHOW TEXT
            _Dest imgText&: Cls , cEmpty
            DrawText sKey, iWhich
            ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
           
            ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
            ' BEGIN UPDATE SCREEN
           
            ' COPY LAYERS TO SCREEN
            _Dest 0: Cls , cBlack
            If imgStars& < -1 Then
                _PutImage , imgStars&, 0
            End If
            If imgText& < -1 Then
                _PutImage , imgText&, 0
            End If
            If imgObjects& < -1 Then
                _PutImage , imgObjects&, 0
            End If
           
            ' UPDATE THE SCREEN
            _Display
           
            ' END UPDATE SCREEN
            ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
           
            ' 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

            ' CYCLE LINE STYLE FOR ACTIVE OBJECT
            iStyleCountNext = iStyleCountNext + 1 ' increment line style counter
            If iStyleCountNext > iStyleCountMax Then
                iStyleCountNext = 0
                iLineStyleIndex = iLineStyleIndex + 1
                If iLineStyleIndex > UBound(m_arrLineStyle) Then
                    iLineStyleIndex = LBound(m_arrLineStyle)
                End If
                lngDashedLineStyle = m_arrLineStyle(iLineStyleIndex)
            End If

            ' CLEAR KEYBOARD BUFFER
            _KeyClear

            ' CONTROL GAME SPEED
            _Limit iFPS
        Wend
        ' END MAIN LOOP
        ' ================================================================================================================================================================

        ' 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
   
    ' CLEAR IMAGES
    _Dest 0: Cls , cBlack
    Screen 0
    If imgStars& < -1 Then _FreeImage imgStars&
    If imgText& < -1 Then _FreeImage imgText&
    If imgObjects& < -1 Then _FreeImage imgObjects&
    If imgTemp& < -1 Then _FreeImage imgTemp&
   
    ' RETURN TO AUTODISPLAY
    _AutoDisplay

End Sub ' DrawVectorObjectTest1

' /////////////////////////////////////////////////////////////////////////////
' SHOW INSTRUMENTS + INSTRUCTIONS
' 800x600 = 40 rows x 100 columns

sub DrawText( _
    sKey as string, _
    iWhich as integer _
    )

    Dim iObject As Integer
    Dim RowNum As Integer
    Dim sFlag As String
    Dim iNum As Integer
   
    RowNum = 0

    Color cWhite, cEmpty
    RowNum = RowNum + 1: PrintAt RowNum, 1, m_sTitle
    RowNum = RowNum + 1
   
    Color cYellow, cEmpty
    RowNum = RowNum + 1: PrintAt RowNum, 1, "----------------------------------------"
    RowNum = RowNum + 1: PrintAt RowNum, 1, "Press 1-6 to select active object."
    RowNum = RowNum + 1: PrintAt RowNum, 1, "Arrow keys move active object."
    RowNum = RowNum + 1: PrintAt RowNum, 1, "Press ESC to quit"
    RowNum = RowNum + 1: PrintAt RowNum, 1, "----------------------------------------"
    RowNum = RowNum + 1

    ' SHOW OBJECTS
    Color cGray, cEmpty
    RowNum = RowNum + 1: PrintAt RowNum, 1, "----------------------------------------"

    iNum = 0
    For iObject = LBound(m_arrObject) To UBound(m_arrObject)
        iNum = iNum + 1
        If m_arrObject(iObject).IsEnabled = TRUE Then
            Color m_arrObject(iObject).PreviewColor, cEmpty
            If iObject = iWhich Then
                'Color cCyan, cEmpty
                sFlag = "-> "
            Else
                'Color cDodgerBlue, cEmpty
                sFlag = "   "
            End If
        Else
            Color cGray, cEmpty
            sFlag = "   "
        End If

        RowNum = RowNum + 1: PrintAt RowNum, 1, "" + _
            sFlag + _
            cstr$(iNum) + ". " + _
            "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) + ")" + _
            ""
    Next iObject

    Color cGray, cEmpty
    RowNum = RowNum + 1: PrintAt RowNum, 1, "----------------------------------------"

    RowNum = RowNum + 2

    '' SHOW ACTIVE OBJECT
    'Color cWhite
    'RowNum = RowNum + 1: PrintAt RowNum, 1, "Object #   : " + cstr$(iWhich)

    ' SHOW INPUT
    Color cLime, cEmpty
    RowNum = RowNum + 1: PrintAt RowNum, 1, "Controls   : " + RightPadString$(sKey, 10, " ") + "   "
   
End Sub ' DrawText

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

' Receives:
' Long iInput1 = value (0-65535) to conver to binary

' Returns a 16 character string of "1" and "0"
' (a 16-bit binary representation of iInput1)

Function BinaryStringFromLong$ (iInput1 As Long)
    Dim sBinary As String
    Dim iInput As Long
    Dim iLoop As Integer
    Dim iNextValue As Long

    sBinary = ""
    iInput = iInput1
    If iInput >= 0 Then
        For iLoop = 15 To 0 Step -1
            iNextValue = 2 ^ iLoop
            If (iInput \ iNextValue) > 0 Then
                sBinary = sBinary + "1"
            Else
                sBinary = sBinary + "0"
            End If
            iInput = iInput Mod iNextValue
        Next iLoop
    End If

    BinaryStringFromLong$ = sBinary
End Function ' BinaryStringFromLong$

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

' Receives:
' String sBitPattern = 16 character string of "1" and "0"
'                      (a 16-bit binary representation)

' Returns the bit pattern converted to a long integer.

Function LongFromBinaryString& (sBitPattern As String)
    Dim sInput As String: sInput = sBitPattern
    Dim iLoop As Integer
    Dim MyLong As Long

    MyLong = 0

    If Len(sInput) >= 16 Then
        For iLoop = 0 To 15
            If Mid$(sInput, 16 - iLoop, 1) = "1" Then
                MyLong = MyLong + (2 ^ iLoop)
            End If
        Next iLoop
    End If

    LongFromBinaryString& = MyLong
End Function ' LongFromBinaryString&

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

' future versions will pull this data from an editable 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

    ' CLEAR OUT EXISTING
    For iObject = LBound(m_arrObject) To UBound(m_arrObject)
        m_arrObject(iObject).IsEnabled = FALSE
    Next iObject
    For iObject = LBound(m_arrLines, 1) To UBound(m_arrLines, 1)
        For iLine = LBound(m_arrLines, 2) To UBound(m_arrLines, 2)
            m_arrLines(iObject, iLine).x1 = 0
            m_arrLines(iObject, iLine).y1 = 0
            m_arrLines(iObject, iLine).x2 = 0
            m_arrLines(iObject, iLine).y2 = 0
            m_arrLines(iObject, iLine).color = cEmpty
            m_arrLines(iObject, iLine).IsLast = TRUE
            m_arrLines(iObject, iLine).IsEnabled = FALSE
        Next iLine
    Next iObject
   
    ' INITIALIZE FROM SAVED
    Restore VectorData
    iObject = 1
    iLine = 1
    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 ' NO MORE DATA AT ALL
            m_arrLines(iObject, iLine).IsEnabled = FALSE
            m_arrLines(iObject, iLine).IsLast = TRUE
            m_arrObject(iObject).IsEnabled = TRUE
            Exit For
        ElseIf b1 = -254 Then ' NO MORE DATA FOR THIS OBJECT
            m_arrLines(iObject, iLine).IsEnabled = FALSE
            m_arrLines(iObject, iLine).IsLast = TRUE
            m_arrObject(iObject).IsEnabled = TRUE
            iObject = iObject + 1
            iLine = 1
            If iObject > UBound(m_arrLines, 1) Then Exit For
        Else
            m_arrLines(iObject, iLine).IsEnabled = TRUE
            m_arrLines(iObject, iLine).IsLast = FALSE
            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)
            iLine = iLine + 1
            If iLine > UBound(m_arrLines, 2) Then Exit For
        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 = yellow
    Data 9,-62,60,-21,255,255,0
    Data 60,-21,62,-3,255,255,0
    Data 62,-3,24,13,255,255,0
    Data 24,13,53,34,255,255,0
    Data 53,34,38,55,255,255,0
    Data 38,55,20,40,255,255,0
    Data 20,40,-37,61,255,255,0
    Data -37,61,-63,15,255,255,0
    Data -63,15,-57,-24,255,255,0
    Data -57,-24,-24,-24,255,255,0
    Data -24,-24,-38,-45,255,255,0
    Data -38,-45,9,-62,255,255,0
    Data 0,0,0,0,-254,-254,-254

    'objmouse = white
    Data 0,-10,6,3,255,255,255
    Data 6,3,1,2,255,255,255
    Data 1,2,1,10,255,255,255
    Data 1,10,-1,10,255,255,255
    Data -1,10,-1,2,255,255,255
    Data -1,2,-6,3,255,255,255
    Data -6,3,0,-10,255,255,255
    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
    'Data 0,18,0,18,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,-254,-254,-254
    'Data 0,0,0,0,-255,-255,-255
   
   
   
   
    'test object
    Data -31,-31,-31,31,255,0,0
    Data -31,31,31,31,255,0,0
    Data 31,31,31,-31,255,0,0
    Data 31,-31,-31,-31,255,0,0
    Data 0,0,0,0,-254,-254,-254
   
    'test object
    Data -31,-31,-31,31,0,0,255
    Data -31,31,31,31,0,0,255
    Data 31,31,31,-31,0,0,255
    Data 31,-31,-31,-31,0,0,255
    Data 0,0,0,0,-254,-254,-254
   
   
   
   
   
    '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 = yellow
    Data 9,-62,60,-21,255,255,0
    Data 60,-21,62,-3,255,255,0
    Data 62,-3,24,13,255,255,0
    Data 24,13,53,34,255,255,0
    Data 53,34,38,55,255,255,0
    Data 38,55,20,40,255,255,0
    Data 20,40,-37,61,255,255,0
    Data -37,61,-63,15,255,255,0
    Data -63,15,-57,-24,255,255,0
    Data -57,-24,-24,-24,255,255,0
    Data -24,-24,-38,-45,255,255,0
    Data -38,-45,9,-62,255,255,0
    Data 0,0,0,0,-254,-254,-254

    'objmouse = white
    Data 0,-10,6,3,255,255,255
    Data 6,3,1,2,255,255,255
    Data 1,2,1,10,255,255,255
    Data 1,10,-1,10,255,255,255
    Data -1,10,-1,2,255,255,255
    Data -1,2,-6,3,255,255,255
    Data -6,3,0,-10,255,255,255
    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
    'Data 0,18,0,18,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,-254,-254,-254
    'Data 0,0,0,0,-255,-255,-255
   
    'test object
    Data -31,-31,-31,31,255,0,0
    Data -31,31,31,31,255,0,0
    Data 31,31,31,-31,255,0,0
    Data 31,-31,-31,-31,255,0,0
    Data 0,0,0,0,-254,-254,-254
   
    'test object
    Data -31,-31,-31,31,0,0,255
    Data -31,31,31,31,0,0,255
    Data 31,31,31,-31,0,0,255
    Data 31,-31,-31,-31,0,0,255
    Data 0,0,0,0,-254,-254,-254
   
   
   
   
   
   
   
   
   
    'test object
    Data -31,-31,-31,31,255,0,0
    Data -31,31,31,31,255,0,0
    Data 31,31,31,-31,255,0,0
    Data 31,-31,-31,-31,255,0,0
    Data 0,0,0,0,-254,-254,-254
   
    'objaster0 = deep purple square
    Data -63,-63,-63,63,96,0,255
    Data -63,63,63,63,96,0,255
    Data 63,63,63,-63,96,0,255
    Data 63,-63,-63,-63,96,0,255
    Data 0,0,0,0,-254,-254,-254
   
    'test object
    Data -31,-31,-31,31,0,0,255
    Data -31,31,31,31,0,0,255
    Data 31,31,31,-31,0,0,255
    Data 31,-31,-31,-31,0,0,255
    Data 0,0,0,0,-254,-254,-254
   
    '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 = yellow
    Data 9,-62,60,-21,255,255,0
    Data 60,-21,62,-3,255,255,0
    Data 62,-3,24,13,255,255,0
    Data 24,13,53,34,255,255,0
    Data 53,34,38,55,255,255,0
    Data 38,55,20,40,255,255,0
    Data 20,40,-37,61,255,255,0
    Data -37,61,-63,15,255,255,0
    Data -63,15,-57,-24,255,255,0
    Data -57,-24,-24,-24,255,255,0
    Data -24,-24,-38,-45,255,255,0
    Data -38,-45,9,-62,255,255,0
    Data 0,0,0,0,-254,-254,-254

    'objmouse = white
    Data 0,-10,6,3,255,255,255
    Data 6,3,1,2,255,255,255
    Data 1,2,1,10,255,255,255
    Data 1,10,-1,10,255,255,255
    Data -1,10,-1,2,255,255,255
    Data -1,2,-6,3,255,255,255
    Data -6,3,0,-10,255,255,255
    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
    'Data 0,18,0,18,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,-254,-254,-254
    'Data 0,0,0,0,-255,-255,-255
   
   
   
   
   
   
   
   
   
    '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 = yellow
    Data 9,-62,60,-21,255,255,0
    Data 60,-21,62,-3,255,255,0
    Data 62,-3,24,13,255,255,0
    Data 24,13,53,34,255,255,0
    Data 53,34,38,55,255,255,0
    Data 38,55,20,40,255,255,0
    Data 20,40,-37,61,255,255,0
    Data -37,61,-63,15,255,255,0
    Data -63,15,-57,-24,255,255,0
    Data -57,-24,-24,-24,255,255,0
    Data -24,-24,-38,-45,255,255,0
    Data -38,-45,9,-62,255,255,0
    Data 0,0,0,0,-254,-254,-254

    'objmouse = white
    Data 0,-10,6,3,255,255,255
    Data 6,3,1,2,255,255,255
    Data 1,2,1,10,255,255,255
    Data 1,10,-1,10,255,255,255
    Data -1,10,-1,2,255,255,255
    Data -1,2,-6,3,255,255,255
    Data -6,3,0,-10,255,255,255
    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
    'Data 0,18,0,18,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,-254,-254,-254
    'Data 0,0,0,0,-255,-255,-255
   
    ''test object
    'Data -31,-31,-31,31,255, 0, 0
    'Data -31,31,31,31,255, 0, 0
    'Data 31,31,31,-31,255, 0, 0
    'Data 31,-31,-31,-31,255, 0, 0
    'Data 0,0,0,0,-254,-254,-254
   
   
   
   
    'FINAL test object
    Data -31,-31,-31,31,255,255,255
    Data -31,31,31,31,255,255,255
    Data 31,31,31,-31,255,255,255
    Data 31,-31,-31,-31,255,255,255
    Data -16,-16,-16,16,255,0,0
    Data -16,16,16,16,0,255,0
    Data 16,16,16,-16,0,0,255
    Data 16,-16,-16,-16,255,255,0
    '    Data 0,0,0,0,-254,-254,-254
    Data 0,0,0,0,-255,-255,-255
End Sub ' InitVectorObjects

' ################################################################################################################################################################
' 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"
   
    DebugPrintTS "started " + RoutineName
   
    Screen _NewImage(iMaxX, iMaxY, 32)
    _KeyClear
   
    DebugPrintTS "InitDxDyTables"
    InitDxDyTables
    DebugPrintTS "InitVariables"
    InitVariables ' Initialize variables
   
    _KeyClear
   
    Do ' main game loop
        Cls ' Clear the form
        GetInput
       
        If m_bEscKey = FALSE Then
            DebugPrintTS "MoveBullets"
            MoveBullets ' Activates the MoveBullets sub
            DebugPrintTS "MoveAllShips"
            MoveAllShips ' Activates the MoveAllShips sub
            DebugPrintTS "MoveEnemy"
            MoveEnemy ' (doesn't do much yet)
            DebugPrintTS "Collisions"
            Collisions ' Activates the Collisions sub
            DebugPrintTS "CheckForEnd"
            CheckForEnd
           
        Else
            ' game cancelled
            m_bGameOver = TRUE
            Exit Do
        End If
       
        If m_bGameOver = FALSE Then
            DebugPrintTS "Shooting"
            Shooting ' Activates the Shooting sub
            DebugPrintTS "DrawEnemy"
            DrawEnemy ' Activates the DrawEnemy sub
            DebugPrintTS "DrawBullets"
            DrawBullets ' Activates the DrawBullets sub
            DebugPrintTS "ShowScore"
            ShowScore ' Display the score, etc.
            DebugPrintTS "DrawAllShips"
            DrawAllShips ' Activates the DrawAllShips sub
           
            'if m_bSimpleEnemies=TRUE then
            '    Respawn ' Activates the Respawn sub
            'end if
        Else
            If AskPlayAgain% = TRUE Then
                DebugPrintTS "AskPlayAgain% = TRUE: InitVariables"
                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

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

Sub OptionsMenu ()
    Dim RoutineName As String: RoutineName = "OptionsMenu"
    Dim bFinished%
    Dim bChanged As Integer
    Dim iLastKey As Integer
   
    ' INITIALIZE
    iLastKey = 0
    bChanged = TRUE
    bFinished% = FALSE
    Do
        If bChanged = TRUE Then
            Cls
            Print "Key   Change             Current"
            Print "- +   Option             Value"
            Print "---   ----------------   -------"
            Print "1 2   How many players   " + cstr$(m_iPlayers)
            Print "3 4   Ships per player   " + cstr$(m_iShipsPerPlayer)
            Print "5 6   Rounds per game    " + IIFSTR$(m_iRoundsPerGame = 0, "Infinite", cstr$(m_iRoundsPerGame))
            Print "7 8   Simple enemies     " + IIFSTR$(m_bSimpleEnemies, "yes", "No")
            Print
            Print "Press ESC to exit."
           
            'ClearKeyboard 1
            _KeyClear
            bChanged = 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 Then
            ' DID PLAYER PRESS ANY KEYS WE KNOW?
           
            If _Button(KeyCode_1%) Then
                If m_iPlayers > MIN_PLAYERS Then
                    m_iPlayers = m_iPlayers - 1
                    bChanged = TRUE
                End If
                iLastKey = KeyCode_1%
            ElseIf _Button(KeyCode_2%) Then
                If m_iPlayers < MAX_PLAYERS Then
                    If ((m_iPlayers + 1) * m_iShipsPerPlayer) <= MAX_PLAYERS Then
                        m_iPlayers = m_iPlayers + 1
                        bChanged = TRUE
                    End If
                End If
                iLastKey = KeyCode_2%
               
            ElseIf _Button(KeyCode_3%) Then
                If m_iShipsPerPlayer > 1 Then
                    m_iShipsPerPlayer = m_iShipsPerPlayer - 1
                    bChanged = TRUE
                End If
                iLastKey = KeyCode_3%
            ElseIf _Button(KeyCode_4%) Then
                If (m_iPlayers * (m_iShipsPerPlayer + 1)) <= MAX_PLAYERS Then
                    m_iShipsPerPlayer = m_iShipsPerPlayer + 1
                    bChanged = TRUE
                End If
                iLastKey = KeyCode_4%
               
               
               
               
            ElseIf _Button(KeyCode_5%) Then
                If m_iRoundsPerGame > 0 Then
                    m_iRoundsPerGame = m_iRoundsPerGame - 1
                Else
                    m_iRoundsPerGame = MAX_ROUNDS
                End If
                bChanged = TRUE
                iLastKey = KeyCode_5%
            ElseIf _Button(KeyCode_6%) Then
                If m_iRoundsPerGame < MAX_ROUNDS Then
                    m_iRoundsPerGame = m_iRoundsPerGame + 1
                Else
                    m_iRoundsPerGame = 0
                End If
                bChanged = TRUE
                iLastKey = KeyCode_6%
               
               
               
               
               
            ElseIf _Button(KeyCode_7%) Then
                If m_bSimpleEnemies = TRUE Then
                    m_bSimpleEnemies = FALSE
                    bChanged = TRUE
                End If
                iLastKey = KeyCode_7%
            ElseIf _Button(KeyCode_8%) Then
                If m_bSimpleEnemies = FALSE Then
                    m_bSimpleEnemies = TRUE
                    bChanged = TRUE
                End If
                iLastKey = KeyCode_8%
               
               
               
            ElseIf _Button(KeyCode_Escape%) Then
                bFinished = TRUE
                iLastKey = KeyCode_Escape%
            End If
        End If ' IF iLastKey = 0
       
        _Limit 30
    Loop Until bFinished = TRUE
   
End Sub ' OptionsMenu

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

Function AskPlayAgain%
    Dim bResult As Integer: bResult = FALSE
    Dim iColCount As Integer
    Dim iRowCount As Integer
    Dim iRow As Integer
    Dim iCol As Integer
    Dim iLoopRow As Integer
    Dim iWidth As Integer
   
    ' Get # text columns + rows
    iRowCount = _Height(0) \ _FontHeight
    iColCount = _Width(0) \ _FontWidth
    iWidth = 18
   
    ' Draw background
    iRow = (iRowCount \ 2)
    iCol = (iColCount \ 2) - (iWidth / 2)
    Color cRed, cRed
    For iLoopRow = (iRow - 2) To (iRow + 2)
        PrintString iLoopRow, iCol, String$(iWidth, " ")
    Next iLoopRow
   
    ' Write prompt
    Color cWhite, cEmpty
    PrintString iRow - 1, iCol, LeftPadString$("GAME OVER", iWidth, " ")
    PrintString iRow + 1, iCol, LeftPadString$("Play again? (y/n)", iWidth, " ")
   
    ' GET USER INPUT
    _KeyClear
    Do
        While _DeviceInput(1): Wend ' Clear and update the keyboard buffer
       
        If _Button(KeyCode_Y%) Then
            bResult = TRUE
            Exit Do
        ElseIf _Button(KeyCode_N%) Then
            bResult = FALSE
            Exit Do
        End If
       
        _Limit 30
    Loop
   
    ' RETURN RESULT
    AskPlayAgain% = bResult
End Function ' AskPlayAgain%

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

Function AskPlayAgain1%
    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

    AskPlayAgain1% = bResult
End Function ' AskPlayAgain1%

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

Sub InitVariables ()
    Dim iShip1 As Integer
    Dim iShip2 As Integer
    Dim iPlayer As Integer
    Dim iEnemy As Integer
    Dim iLoop1 As Integer
    Dim iMinShip As Integer
    Dim iMaxShip As Integer
    Dim iTurn As Integer
    Dim iSpread As Integer
    Dim iHalf As Integer
    Dim iDivisor As Integer
    Dim bDone As Integer
    Dim in$

    ' 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
       
        ' SIZE ARRAY FOR PLAYERS (who is controlling the ships)
        ReDim m_arrPlayer(1 To 16) As PlayerType
       
        ' SIZE ARRAY FOR SHIPS
        ReDim m_arrShip(1 To 16) As ShipType
       
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        ' BEGIN WHO CONTROLS WHAT
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        ' RESET SHIP TO PLAYER MAPPING:
        For iShip1 = 1 To 16
            m_arrShip(iShip1).PlayerNum = 0
            m_arrShip(iShip1).shields = 0
        Next iShip1
       
        ' MAP PLAYERS TO SHIPS AND COUNT TOTAL # OF SHIPS
        m_iNumShips = 0
        For iPlayer = 1 To m_iPlayers
            iMinShip = 0
            iMaxShip = 0
           
            For iShip1 = 1 To m_iShipsPerPlayer
                ' GET SHIP #
                m_iNumShips = m_iNumShips + 1
               
                ' ASSIGN SHIP TO PLAYER
                m_arrShip(m_iNumShips).PlayerNum = iPlayer
               
                ' SAVE FIRST & LAST SHIP INDEX FOR CURRENT PLAYER
                If iMinShip = 0 Then
                    iMinShip = m_iNumShips
                End If
                If m_iNumShips > iMaxShip Then
                    iMaxShip = m_iNumShips
                End If
            Next iShip1
           
            ' INITIALIZE PLAYER FIRST/LAST/CURRENT SHIP
            m_arrPlayer(iPlayer).FirstShip = iMinShip
            m_arrPlayer(iPlayer).LastShip = iMaxShip
            m_arrPlayer(iPlayer).WhichShip = iMinShip
           
            ' RESET BUTTON #2 CONTROL STATUS
            m_arrPlayer(iPlayer).Button2_IsReady = TRUE
        Next iPlayer
       
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        ' END WHO CONTROLS WHAT
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
       
        ' CLEAR CONTROL BUFFER FOR SHIPS
        For iShip1 = 1 To 16
            ' Clear input flags
            m_arrShip(iShip1).Left_IsPressed = FALSE
            m_arrShip(iShip1).Right_IsPressed = FALSE
            m_arrShip(iShip1).Up_IsPressed = FALSE
            m_arrShip(iShip1).Down_IsPressed = FALSE
            m_arrShip(iShip1).Button1_IsPressed = FALSE
            m_arrShip(iShip1).Button2_IsPressed = FALSE
        Next iShip1
       
        InitColors
       
        MapControls
       
        ' Set the starting positions of the ships
        For iShip1 = 1 To m_iNumShips
            ' FOR NOW JUST NAME EACH PLAYER A LETTER OF THE ALPHABET
            m_arrShip(iShip1).name = Chr$(iShip1 + 64)
           
            ' RESET SCORE
            m_arrShip(iShip1).score = 0
            m_arrShip(iShip1).level = 0
           
            ' EVERYONE STARTS WITH SAME SHIELDS
            ' TODO: GET THIS VALUE FROM OPTIONS
            m_arrShip(iShip1).shields = NUM_SHIELDS
           
            ' 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(iShip1).radius = SHIP_RADIUS
           
            ' TODO: ADD OPTIONS FOR FUEL AND AMMO, FOR NOW NOT USED...
            m_arrShip(iShip1).fuel = 100
            If m_iNumShips > 1 Then
                m_arrShip(iShip1).ammo = m_iNumShips * 2 ' 2 shots per opponent
            Else
                m_arrShip(iShip1).ammo = 10
            End If

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

            ' SHOP NOT MOVING
            ' TODO: MAYBE ADD OPTION TO START PLAYERS DRIFTING IN A RANDOM DIRECTION?
            m_arrShip(iShip1).dx = 0
            m_arrShip(iShip1).dy = 0
            m_arrShip(iShip1).vx = 0
            m_arrShip(iShip1).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(iShip1).ShootTime = iFPS \ 4
            m_arrShip(iShip1).ShootCount = m_arrShip(iShip1).ShootTime + 1

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

' /////////////////////////////////////////////////////////////////////////////
' TODO: allow players to select colors as part of ship editor
'       and store it in a settings file

Sub InitColors
    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 Sub ' InitColors

' /////////////////////////////////////////////////////////////////////////////
' Initialize control mapping

' TODO: allow players to map their own controls (keyboard, gamepad, mouse, etc.)
'       and store it in a settings file

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

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

Sub GetInput ()
    Dim iPlayer As Integer
    Dim iShip1 As Integer
    Dim iFirstShip As Integer
    Dim iLastShip As Integer
    While _DeviceInput(1): Wend ' clear and update the keyboard buffer
   
    ' DID THEY PRESS ESCAPE?
    If _Button(KeyCode_Escape%) Then
        m_bEscKey = TRUE
    Else
        m_bEscKey = FALSE
    End If
   
    ' DETECT KEYS FOR EACH SHIP
    For iPlayer = 1 To m_iPlayers
       
        ' IF PLAYER HAS SHIPS, DETECT KEYS
        If m_arrPlayer(iPlayer).WhichShip > -1 Then
           
            ' IF PLAYER PRESSES BUTTON #2, CYCLE WHICH SHIP THEY CONTROL
            If _Button(m_arrPlayer(iPlayer).Button2_KeyCode) Then
                ' IS BUTTON ENABLED?
                If m_arrPlayer(iPlayer).Button2_IsReady = TRUE Then
                   
                    ' DISABLE ALL EXISTING BUTTONS FOR THIS PLAYER'S SHIPS
                    For iShip1 = m_arrPlayer(iPlayer).FirstShip To m_arrPlayer(iPlayer).LastShip
                        ClearInputBuffer iShip1
                    Next iShip1
                   
                    ' CHANGE WHICH SHIP PLAYER CONTROLS
                    If m_arrPlayer(iPlayer).WhichShip = 0 Then
                        ' SWITCH CONTROL TO FIRST SHIP
                        m_arrPlayer(iPlayer).WhichShip = m_arrPlayer(iPlayer).FirstShip
                    Else
                        If m_arrPlayer(iPlayer).WhichShip = m_arrPlayer(iPlayer).LastShip Then
                            ' SWITCH CONTROL TO ALL SHIPS
                            m_arrPlayer(iPlayer).WhichShip = 0
                        Else
                            ' SWITCH CONTROL TO NEXT WORKING SHIP
                            iShip1 = m_arrPlayer(iPlayer).WhichShip
                            Do
                                iShip1 = iShip1 + 1
                                If iShip1 > m_arrPlayer(iPlayer).LastShip Then
                                    iShip1 = m_arrPlayer(iPlayer).FirstShip
                                End If
                                If m_arrShip(iShip1).shields > 0 Then
                                    Exit Do
                                ElseIf iShip1 = m_arrPlayer(iPlayer).WhichShip Then
                                    Exit Do
                                End If
                            Loop
                            m_arrPlayer(iPlayer).WhichShip = iShip1
                        End If
                    End If
                   
                    ' DISABLE BUTTON UNTIL IT IS RELEASED
                    m_arrPlayer(iPlayer).Button2_IsReady = FALSE
                End If
            Else
                ' BUTTON HAS BEEN RELEASED, RE-ENABLE IT
                m_arrPlayer(iPlayer).Button2_IsReady = TRUE
            End If
           
            ' WHICH SHIPS TO GET INPUT FOR?
            If m_arrPlayer(iPlayer).WhichShip = 0 Then
                ' ALL SHIPS
                iFirstShip = m_arrPlayer(iPlayer).FirstShip
                iLastShip = m_arrPlayer(iPlayer).LastShip
            Else
                ' JUST THE CURRENT ONE
                iFirstShip = m_arrPlayer(iPlayer).WhichShip
                iLastShip = m_arrPlayer(iPlayer).WhichShip
            End If
           
            ' GET INPUT FOR SHIP(S)
            For iShip1 = iFirstShip To iLastShip
                ' IGNORE DESTROYED SHIPS
                If m_arrShip(iShip1).shields > 0 Then
                    If _Button(m_arrPlayer(iPlayer).Left_KeyCode) Then
                        m_arrShip(iShip1).Left_IsPressed = TRUE
                        m_arrShip(iShip1).Right_IsPressed = FALSE
                    ElseIf _Button(m_arrPlayer(iPlayer).Right_KeyCode) Then
                        m_arrShip(iShip1).Left_IsPressed = FALSE
                        m_arrShip(iShip1).Right_IsPressed = TRUE
                    Else
                        m_arrShip(iShip1).Left_IsPressed = FALSE
                        m_arrShip(iShip1).Right_IsPressed = FALSE
                    End If
                   
                    If _Button(m_arrPlayer(iPlayer).Up_KeyCode) Then
                        m_arrShip(iShip1).Up_IsPressed = TRUE
                        m_arrShip(iShip1).Down_IsPressed = FALSE
                    ElseIf _Button(m_arrPlayer(iPlayer).Down_KeyCode) Then
                        m_arrShip(iShip1).Up_IsPressed = FALSE
                        m_arrShip(iShip1).Down_IsPressed = TRUE
                    Else
                        m_arrShip(iShip1).Up_IsPressed = FALSE
                        m_arrShip(iShip1).Down_IsPressed = FALSE
                    End If

                    If _Button(m_arrPlayer(iPlayer).Button1_KeyCode) Then
                        m_arrShip(iShip1).Button1_IsPressed = TRUE
                    Else
                        m_arrShip(iShip1).Button1_IsPressed = FALSE
                    End If

                    'If _Button(m_arrPlayer(iPlayer).Button2_KeyCode) Then
                    '    m_arrShip(iShip1).Button2_IsPressed = TRUE
                    'Else
                    '    m_arrShip(iShip1).Button2_IsPressed = FALSE
                    'End If
                End If
            Next iShip1
           
        End If
    Next iPlayer
   
    ' CLEAR KEYBOARD BUFFER
    _KeyClear

End Sub ' GetInput

' /////////////////////////////////////////////////////////////////////////////
' DISABLE ALL EXISTING BUTTONS FOR THIS SHIP
' Clear input flags

Sub ClearInputBuffer (iShip1 As Integer)
    m_arrShip(iShip1).Left_IsPressed = FALSE
    m_arrShip(iShip1).Right_IsPressed = FALSE
    m_arrShip(iShip1).Up_IsPressed = FALSE
    m_arrShip(iShip1).Down_IsPressed = FALSE
    m_arrShip(iShip1).Button1_IsPressed = FALSE
    m_arrShip(iShip1).Button2_IsPressed = FALSE
End Sub ' ClearInputBuffer

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

' TODO: improve collision checking to handle different shape polygons, etc.
'       using IsIntersection function by MasterGy

Sub Collisions ()
    Dim iShip1 As Integer
    Dim iShip2 As Integer
    Dim iEnemy1 As Integer
    Dim iBullet1 As Integer
    Dim iBullet2 As Integer
   
    ' ================================================================================================================================================================
    ' BEGIN check for bullet collisions
    ' ================================================================================================================================================================
   
    ' CHECK ALL BULLETS
    For iBullet1 = 0 To UBound(m_arrBullet)
       
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        ' BEGIN bullet hits bullet
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        ' IS BULLET ALIVE?
        If m_arrBullet(iBullet1).alive = TRUE Then
           
            ' CHECK ALL (OTHER) BULLETS
            For iBullet2 = 0 To UBound(m_arrBullet)
               
                ' NOT ITSELF!
                If iBullet2 <> iBullet1 Then
                   
                    ' CAN BULLETS STOP BULLETS?
                    If BULLETS_STOP_BULLETS = TRUE Then
                       
                        ' BULLET HIT BULLET?
                        If GetDist(m_arrBullet(iBullet2).xPos, m_arrBullet(iBullet2).yPos, m_arrBullet(iBullet1).xPos, m_arrBullet(iBullet1).yPos) <= BULLET_RADIUS Then
                            ' BOTH SHOTS DESTROYED
                            m_arrBullet(iBullet1).alive = FALSE ' Destroy the bullet
                            m_arrBullet(iBullet2).alive = FALSE ' Destroy the other bullet
                           
                            ' QUIT CHECKING BULLETS
                            Exit For
                        End If
                    End If
                End If
            Next iBullet2
        End If
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        ' END bullet hits bullet
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
       
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        ' BEGIN bullet hits ship
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        ' IS BULLET ALIVE?
        If m_arrBullet(iBullet1).alive = TRUE Then
           
            ' CHECK ALL SHIPS
            For iShip1 = 1 To m_iNumShips
               
                ' IS SHIP ALIVE?
                If m_arrShip(iShip1).shields > 0 Then
                   
                    ' BULLET HITS SHIP?
                    If GetDist(m_arrShip(iShip1).xPos, m_arrShip(iShip1).yPos, m_arrBullet(iBullet1).xPos, m_arrBullet(iBullet1).yPos) <= m_arrShip(iShip1).radius Then
                       
                        ' STOP BULLET
                        m_arrBullet(iBullet1).alive = FALSE ' Destroy the bullet
                       
                        ' DO DAMAGE
                        ' TODO: DIFFERENT DAMAGE DEPENDING ON WHETHER IT'S A PLAYER OR ENEMY BULLET, WEAPON TYPE, ETC.
                        m_arrShip(iShip1).shields = m_arrShip(iShip1).shields - BULLET_DAMAGE ' Take Damage
                       
                        ' IS SHIP DEAD?
                        If m_arrShip(iShip1).shields < 1 Then
                           
                            ' CLEAR INPUT FOR THIS SHIP
                            ClearInputBuffer iShip1
                           
                            ' ADJUST ROSTER FOR THIS PLAYER
                            AdjustRoster m_arrShip(iShip1).PlayerNum
                           
                            ' PLAYER SCORES IF OPPONENT KILLED
                            If m_arrBullet(iBullet1).kind = "SHIP" Then
                                m_arrShip(m_arrBullet(iBullet1).owner).score = m_arrShip(m_arrBullet(iBullet1).owner).score + 100
                            End If
                        Else
                            ' TODO: award points for hit but not kill?
                        End If
                       
                        ' QUIT CHECKING SHIPS
                        Exit For
                    End If
                   
                End If
            Next iShip1
        End If
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        ' END bullet hits ship
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
       
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        ' BEGIN bullet hits enemy
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        ' IS THIS BULLET STILL ALIVE?
        If m_arrBullet(iBullet1).alive = TRUE Then
           
            ' CHECK ENEMIES
            For iEnemy1 = 0 To UBound(m_arrEnemy)
               
                ' IS ENEMY ALIVE?
                If m_arrEnemy(iEnemy1).alive = TRUE Then
                   
                    ' BULLET HITS ENEMY?
                    If GetDist(m_arrEnemy(iEnemy1).xPos, m_arrEnemy(iEnemy1).yPos, m_arrBullet(iBullet1).xPos, m_arrBullet(iBullet1).yPos) <= m_arrEnemy(iEnemy1).radius Then
                       
                        ' STOP BULLET
                        m_arrBullet(iBullet1).alive = FALSE ' Destroy the bullet
                       
                        ' DO DAMAGE
                        ' TODO: DIFFERENT DAMAGE DEPENDING ON WHETHER IT'S A PLAYER OR ENEMY BULLET, WEAPON TYPE, ETC.
                        m_arrEnemy(iEnemy1).life = m_arrEnemy(iEnemy1).life - BULLET_DAMAGE ' Enemy take damage
                       
                        ' IS ENEMY DEAD?
                        If m_arrEnemy(iEnemy1).life <= 0 Then
                           
                            ' DESTROY IT
                            m_arrEnemy(iEnemy1).alive = FALSE
                           
                            ' PLAYER SCORES IF ENEMY KILLED
                            If m_arrBullet(iBullet1).kind = "SHIP" Then
                                m_arrShip(m_arrBullet(iBullet1).owner).score = m_arrShip(m_arrBullet(iBullet1).owner).score + 10
                                'm_iScore = m_iScore + 10
                            End If
                        End If
                       
                        ' QUIT CHECKING ENEMIES
                        Exit For
                       
                    End If
                End If
            Next iEnemy1
        End If
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        ' END bullet hits enemy
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
       
    Next iBullet1
    ' ================================================================================================================================================================
    ' END check for bullet collisions
    ' ================================================================================================================================================================
   
    ' ================================================================================================================================================================
    ' BEGIN check for ship hit enemy
    ' ================================================================================================================================================================
    ' CHECK ENEMIES
    For iEnemy1 = 0 To UBound(m_arrEnemy)
        ' IS ENEMY ALIVE?
        If m_arrEnemy(iEnemy1).alive = TRUE Then
           
            ' CHECK EACH SHIP
            For iShip1 = 1 To m_iNumShips
               
                ' IS SHIP ALIVE?
                If m_arrShip(iShip1).shields > 0 Then
                   
                    ' SHIP HITS ENEMY?
                    If GetDist(m_arrShip(iShip1).xPos, m_arrShip(iShip1).yPos, m_arrEnemy(iEnemy1).xPos, m_arrEnemy(iEnemy1).yPos) <= m_arrEnemy(iEnemy1).radius Then
                        ' KILL SHIP
                        m_arrShip(iShip1).shields = 0 ' The ship has no shields/Dead
                       
                        ' KILL ENEMY
                        m_arrEnemy(iEnemy1).life = 0 ' The enemy has no life/Dead
                        m_arrEnemy(iEnemy1).alive = FALSE
                       
                        ' CLEAR CONTROL BUFFER FOR SHIP
                        ClearInputBuffer iShip1
                       
                        ' ADJUST ROSTER
                        AdjustRoster m_arrShip(iShip1).PlayerNum
                       
                        ' PLAYER SCORES
                        'm_iScore = m_iScore + 10
                        m_arrShip(iShip1).score = m_arrShip(iShip1).score + 10
                       
                        ' QUIT CHECKING SHIPS
                        Exit For
                    End If
                End If
            Next iShip1
        End If
    Next iEnemy1
    ' ================================================================================================================================================================
    ' END check for ship hit enemy
    ' ================================================================================================================================================================
   
    ' ================================================================================================================================================================
    ' BEGIN check for ship colliding with ship
    ' ================================================================================================================================================================
    ' CHECK SHIPS
    For iShip1 = 1 To m_iNumShips
        ' IS SHIP ALIVE?
        If m_arrShip(iShip1).shields > 0 Then
            ' CHECK EACH SHIP
            For iShip2 = 1 To m_iNumShips
                ' NOT THIS SHIP
                If iShip1 <> iShip2 Then
                    ' IS SHIP ALIVE?
                    If m_arrShip(iShip2).shields > 0 Then
                        ' SHIP HIT SHIP?
                        If GetDist(m_arrShip(iShip1).xPos, m_arrShip(iShip1).yPos, m_arrShip(iShip2).xPos, m_arrShip(iShip2).yPos) <= m_arrShip(iShip1).radius Then
                           
                            ' KILL BOTH SHIPS
                            ' TODO: MAYBE SHIPS DON'T JUST BLOW UP WHEN THEY COLLIDE, ADD OPTIONS FOR DAMAGE, BOUNCE OFF EACH OTHER, ETC.
                            m_arrShip(iShip1).shields = 0
                            m_arrShip(iShip2).shields = 0
                           
                            ' CLEAR CONTROL BUFFER FOR BOTH SHIPS
                            ClearInputBuffer iShip1
                            ClearInputBuffer iShip2
                           
                            ' ADJUST ROSTER
                            AdjustRoster m_arrShip(iShip1).PlayerNum
                            AdjustRoster m_arrShip(iShip2).PlayerNum
                           
                            ' SCORE ONE LAST TIME!
                            m_arrShip(iShip1).score = m_arrShip(iShip1).score + 100
                            m_arrShip(iShip2).score = m_arrShip(iShip2).score + 100
                           
                        End If
                    End If
                End If
            Next iShip2
        End If
    Next iShip1
    ' ================================================================================================================================================================
    ' END check for ship colliding with ship
    ' ================================================================================================================================================================
   
End Sub ' Collisions

' /////////////////////////////////////////////////////////////////////////////
' When a ship is destroyed, updates that player's first, last & current ship
' to next available ship, else sets to -1 meaning none left.

' example: AdjustRoster m_arrShip(iShip1).PlayerNum

Sub AdjustRoster (iPlayer As Integer)
    Dim iShip2 As Integer
    Dim iFirstShip As Integer: iFirstShip = 0
    Dim iLastShip As Integer: iLastShip = 0
   
    ' Only worry about this if >1 ships per player in the current game
    If m_iShipsPerPlayer > 1 Then
       
        ' Make sure there are ships
        If m_arrPlayer(iPlayer).FirstShip > 0 And m_arrPlayer(iPlayer).LastShip > 0 Then
           
            ' Find first working ship in roster
            For iShip2 = m_arrPlayer(iPlayer).FirstShip To m_arrPlayer(iPlayer).LastShip
                If m_arrShip(iShip2).shields > 0 Then
                    iFirstShip = iShip2
                    Exit For
                End If
            Next iShip2
           
            ' Any ships left?
            If iFirstShip > 0 Then
                ' Find last working ship in roster
                For iShip2 = m_arrPlayer(iPlayer).LastShip To m_arrPlayer(iPlayer).FirstShip Step -1
                    If m_arrShip(iShip2).shields > 0 Then
                        iLastShip = iShip2
                        Exit For
                    End If
                Next iShip2
               
                ' Update first & last
                m_arrPlayer(iPlayer).FirstShip = iFirstShip
                m_arrPlayer(iPlayer).LastShip = iLastShip
               
                ' If current is a single ship, move to next available
                If m_arrPlayer(iPlayer).WhichShip > 0 Then
                    m_arrPlayer(iPlayer).WhichShip = iFirstShip
                End If
               
            Else
                ' All dead, all dead
                m_arrPlayer(iPlayer).FirstShip = 0
                m_arrPlayer(iPlayer).LastShip = 0
                m_arrPlayer(iPlayer).WhichShip = -1
            End If
        Else
            ' Already all dead!
            m_arrPlayer(iPlayer).FirstShip = 0
            m_arrPlayer(iPlayer).LastShip = 0
            m_arrPlayer(iPlayer).WhichShip = -1
        End If
       
    End If
   
End Sub ' AdjustRoster

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

Sub Shooting ()
    Dim iShip1 As Integer
    Dim iShip2 As Integer
    Dim iEnemy1 As Integer
    Dim iBullet1 As Integer
    Dim iBullet2 As Integer
    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 iShip1 = 1 To m_iNumShips
        If m_arrShip(iShip1).Button1_IsPressed = TRUE Then
            ' Has the gun cooled down yet (prevent bullet being created every 25 milliseconds)
           
            If m_arrShip(iShip1).ShootCount > m_arrShip(iShip1).ShootTime Then
                m_arrShip(iShip1).ShootCount = 0
               
                iFreeSpot = -1
               
                For iBullet1 = 0 To UBound(m_arrBullet)
                    ' Check whether it can use another bullet or not
                    If m_arrBullet(iBullet1).alive = FALSE Then
                        ' if so use the dead bullet
                        iFreeSpot = iBullet1
                        Exit For
                    End If
                Next iBullet1
               
                ' 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 = iShip1 ' identify player that fired shot
                m_arrBullet(iFreeSpot).alive = TRUE ' The bullet is alive
                m_arrBullet(iFreeSpot).xPos = m_arrShip(iShip1).xPos ' the bullet is created where the ship is
                m_arrBullet(iFreeSpot).yPos = m_arrShip(iShip1).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(iShip1).speed * Sin(m_arrShip(iShip1).heading) + BULLET_SPEED * Sin(m_arrShip(iShip1).facing)
                sngYComp = m_arrShip(iShip1).speed * Cos(m_arrShip(iShip1).heading) + BULLET_SPEED * Cos(m_arrShip(iShip1).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(iShip1).radius + 1) * Sin(m_arrBullet(iFreeSpot).heading))
                m_arrBullet(iFreeSpot).yPos = m_arrBullet(iFreeSpot).yPos - ((m_arrShip(iShip1).radius + 1) * Cos(m_arrBullet(iFreeSpot).heading))
               
            End If
        End If
    Next iShip1
   
    ' ENEMIES SHOOT
    For iEnemy1 = 0 To UBound(m_arrEnemy)
        ' Check whether the enemy is alive
        If m_arrEnemy(iEnemy1).alive = TRUE Then
            ' Check whether the enemy will fire or not
            If Int(Rnd * 100 + 1) = 1 Then
                iFreeSpot = -1
               
                For iBullet1 = 0 To UBound(m_arrBullet)
                    ' Check whether the enemy will use an old bullet
                    If m_arrBullet(iBullet1).alive = FALSE Then
                        ' If so iFreeSpot is the old bullet
                        iFreeSpot = iBullet1
                        Exit For
                    End If
                Next iBullet1
               
                ' 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 iShip1 = 1 To m_iNumShips
                    If m_arrShip(iShip1).shields > 0 Then
                        sngCheckDistance = GetDist(m_arrEnemy(iEnemy1).xPos, m_arrEnemy(iEnemy1).yPos, m_arrShip(iShip1).xPos, m_arrShip(iShip1).yPos)
                        If sngCheckDistance < sngShortestDistance Then
                            sngShortestDistance = sngCheckDistance
                            iClosestPlayer = iShip1
                        End If
                    End If
                Next iShip1
               
                ' 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 = iEnemy1 ' identify enemy that fired shot
                   
                    ' Aim the shot at the player
                    m_arrBullet(iFreeSpot).heading = GetAngle(m_arrEnemy(iEnemy1).xPos, m_arrEnemy(iEnemy1).yPos, m_arrShip(iClosestPlayer).xPos, m_arrShip(iClosestPlayer).yPos)
                   
                    m_arrBullet(iFreeSpot).xPos = m_arrEnemy(iEnemy1).xPos ' Create the bullet where the enemy is
                    m_arrBullet(iFreeSpot).yPos = m_arrEnemy(iEnemy1).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(iEnemy1).radius + 1) * Sin(m_arrBullet(iFreeSpot).heading))
                    m_arrBullet(iFreeSpot).yPos = m_arrBullet(iFreeSpot).yPos - ((m_arrEnemy(iEnemy1).radius + 1) * Cos(m_arrBullet(iFreeSpot).heading))
                End If
               
            End If
        End If
    Next iEnemy1
   
End Sub ' Shooting

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

Sub DrawEnemy ()
    Dim iShip1 As Integer
    Dim iEnemy1 As Integer
   
    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
   
    Dim sngPercentAlive As Single
   
    For iEnemy1 = 0 To UBound(m_arrEnemy)
        ' Is this enemy alive
        If m_arrEnemy(iEnemy1).alive = TRUE Then
            ' Determine percentage of life still left in this enemy
            sngPercentAlive = m_arrEnemy(iEnemy1).life / m_arrEnemy(iEnemy).maxlife
           
            ' Color based on damage
            If sngPercentAlive > 0.85 Then
                iColor = cWhite
            ElseIf sngPercentAlive > 0.71 Then
                iColor = cYellow
            ElseIf sngPercentAlive > 0.57 Then
                iColor = cGold
            ElseIf sngPercentAlive > 0.43 Then
                iColor = cOrange
            ElseIf sngPercentAlive > 0.29 Then
                iColor = cDarkOrange
            ElseIf sngPercentAlive > 0.14 Then
                iColor = cOrangeRed
            Else
                iColor = cRed
            End If
           
            ' Draw body
            ' CIRCLE (x, y), radius, color
            'DrawCircleSolid iX, iY, 8, cRed
            Circle (m_arrEnemy(iEnemy1).xPos, m_arrEnemy(iEnemy1).yPos), m_arrEnemy(iEnemy1).radius, iColor
           
            ' Determine closest player
            sngShortestDistance = iMaxX + iMaxY + 1
            iClosestPlayer = 0
            For iShip1 = 1 To m_iNumShips
                If m_arrShip(iShip1).shields > 0 Then
                    sngCheckDistance = GetDist(m_arrEnemy(iEnemy1).xPos, m_arrEnemy(iEnemy1).yPos, m_arrShip(iShip1).xPos, m_arrShip(iShip1).yPos)
                    If sngCheckDistance < sngShortestDistance Then
                        sngShortestDistance = sngCheckDistance
                        iClosestPlayer = iShip1
                    End If
                End If
            Next iShip1
           
            ' Draw "eye"
            If iClosestPlayer > 0 Then
                sngHeading = GetAngle(m_arrEnemy(iEnemy1).xPos, m_arrEnemy(iEnemy1).yPos, m_arrShip(iClosestPlayer).xPos, m_arrShip(iClosestPlayer).yPos)
                iX = m_arrEnemy(iEnemy1).xPos
                iY = m_arrEnemy(iEnemy1).yPos
                iRadius = m_arrEnemy(iEnemy1).radius / 3
                iX = iX + (m_arrEnemy(iEnemy1).radius - iRadius) * Sin(sngHeading)
                iY = iY - (m_arrEnemy(iEnemy1).radius - iRadius) * Cos(sngHeading)
                Circle (iX, iY), iRadius, iColor
            End If
           
        End If
    Next iEnemy1
End Sub ' DrawEnemy

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

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

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

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

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

' /////////////////////////////////////////////////////////////////////////////
' Process input + move all ships

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

' /////////////////////////////////////////////////////////////////////////////
' Process input + move the ship
' TODO: fix this to make the movement more natural...

Sub MoveShip (iShip1 As Integer)
    Dim sngXComp As Single
    Dim sngYComp As Single
   
    ' If the left key is pressed then rotate the ship left
    If m_arrShip(iShip1).Left_IsPressed = TRUE Then
       
        ' ORIGINAL METHOD:
        If TRUE = TRUE Then
            m_arrShip(iShip1).facing = m_arrShip(iShip1).facing - PI / TURN_SPEED
        End If
       
        ' NON-TRIG METHOD:
        If TRUE = FALSE Then
            m_arrShip(iShip1).dx = m_arrShip(iShip1).dx - 1
            If m_arrShip(iShip1).dx < 0 Then m_arrShip(iShip1).dx = 143
            m_arrShip(iShip1).dy = m_arrShip(iShip1).dy - 1
            If m_arrShip(iShip1).dy < 0 Then m_arrShip(iShip1).dy = 143
        End If
    End If
   
    ' If the Right key is pressed then rotate the ship right
    If m_arrShip(iShip1).Right_IsPressed = TRUE Then
       
        ' ORIGINAL METHOD:
        If TRUE = TRUE Then
            m_arrShip(iShip1).facing = m_arrShip(iShip1).facing + PI / TURN_SPEED
        End If
       
        ' NON-TRIG METHOD:
        If TRUE = FALSE Then
            m_arrShip(iShip1).dx = m_arrShip(iShip1).dx + 1
            If m_arrShip(iShip1).dx > 143 Then m_arrShip(iShip1).dx = 0
            m_arrShip(iShip1).dy = m_arrShip(iShip1).dy + 1
            If m_arrShip(iShip1).dy > 143 Then m_arrShip(iShip1).dy = 0
        End If
    End If
   
    ' If the up key is pressed then and accelerate it in the direction the ship is facing
    If m_arrShip(iShip1).Up_IsPressed = TRUE Then
       
        ' ORIGINAL METHOD:
        If TRUE = TRUE Then
            ' Determine the X and Y components of the resultant vector
            sngXComp = m_arrShip(iShip1).speed * Sin(m_arrShip(iShip1).heading) + SHIP_ACCEL * Sin(m_arrShip(iShip1).facing)
            sngYComp = m_arrShip(iShip1).speed * Cos(m_arrShip(iShip1).heading) + SHIP_ACCEL * Cos(m_arrShip(iShip1).facing)
           
            ' Determine the resultant speed
            m_arrShip(iShip1).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(iShip1).heading = Atn(sngXComp / sngYComp)
            End If
            If Sgn(sngYComp) < 0 Then
                m_arrShip(iShip1).heading = Atn(sngXComp / sngYComp) + PI
            End If
           
        End If
       
        ' NON-TRIG METHOD:
        ' ACCELERATE X AND Y DIRECTIONS BASED ON WHICH DIRECTION WE'RE POINTING
        If TRUE = FALSE Then
            m_arrShip(iShip1).vx = m_arrShip(iShip1).vx + (m_arrDX(m_arrShip(iShip1).dx) * SHIP_ACCEL)
            m_arrShip(iShip1).vy = m_arrShip(iShip1).vy + (m_arrDY(m_arrShip(iShip1).dy) * SHIP_ACCEL)
        End If
    End If
   
    ' If the down key is pressed then and accelerate the ship in the opposite direction it is facing
    ' ORIGINAL METHOD:
    If TRUE = TRUE Then
        If m_arrShip(iShip1).Down_IsPressed = TRUE And m_arrShip(iShip1).speed > -MAX_SPEED Then
            ' Determine the X and Y components of the resultant vector
            sngXComp = m_arrShip(iShip1).speed * Sin(m_arrShip(iShip1).heading) - SHIP_ACCEL * Sin(m_arrShip(iShip1).facing)
            sngYComp = m_arrShip(iShip1).speed * Cos(m_arrShip(iShip1).heading) - SHIP_ACCEL * Cos(m_arrShip(iShip1).facing)
       
            ' Determine the resultant speed
            m_arrShip(iShip1).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(iShip1).heading = Atn(sngXComp / sngYComp)
            End If
            If Sgn(sngYComp) < 0 Then
                m_arrShip(iShip1).heading = Atn(sngXComp / sngYComp) + PI
            End If
        End If
    End If
   
    ' NON-TRIG METHOD:
    ' Don't let the ship go faster then the max speed
    If TRUE = FALSE Then
        If m_arrShip(iShip1).vx < 0 - MAX_SPEED Then
            m_arrShip(iShip1).vx = 0 - MAX_SPEED
        ElseIf m_arrShip(iShip1).vx > MAX_SPEED Then
            m_arrShip(iShip1).vx = MAX_SPEED
        End If
        If m_arrShip(iShip1).vy < 0 - MAX_SPEED Then
            m_arrShip(iShip1).vy = 0 - MAX_SPEED
        ElseIf m_arrShip(iShip1).vy > MAX_SPEED Then
            m_arrShip(iShip1).vy = MAX_SPEED
        End If
    End If
   
    ' ORIGINAL METHOD:
    If TRUE = TRUE Then
        If m_arrShip(iShip1).speed > MAX_SPEED Then
            m_arrShip(iShip1).speed = MAX_SPEED
        End If
    End If
   
    ' Move the ship
    ' ORIGINAL METHOD:
    If TRUE = TRUE Then
        m_arrShip(iShip1).xPos = m_arrShip(iShip1).xPos + m_arrShip(iShip1).speed * Sin(m_arrShip(iShip1).heading)
        m_arrShip(iShip1).yPos = m_arrShip(iShip1).yPos - m_arrShip(iShip1).speed * Cos(m_arrShip(iShip1).heading)
    End If
   
    ' NON-TRIG METHOD:
    If TRUE = FALSE Then
        m_arrShip(iShip1).xPos = m_arrShip(iShip1).xPos + m_arrShip(iShip1).vx
        m_arrShip(iShip1).yPos = m_arrShip(iShip1).yPos + m_arrShip(iShip1).vy
    End If
   
    ' Keep the ship inside the form
    If m_arrShip(iShip1).xPos < iMinX Then
        m_arrShip(iShip1).xPos = iMaxX
    End If
    If m_arrShip(iShip1).xPos > iMaxX Then
        m_arrShip(iShip1).xPos = iMinX
    End If
    If m_arrShip(iShip1).yPos < iMinY Then
        m_arrShip(iShip1).yPos = iMaxY
    End If
    If m_arrShip(iShip1).yPos > iMaxY Then
        m_arrShip(iShip1).yPos = iMinY
    End If
   
    '' Did player hit button #2?
    'If m_arrShip(iShip1).Button2_IsPressed = TRUE Then
    '    m_arrShip(iShip1).shields = m_arrShip(iShip1).shields + 10
    'End If
   
End Sub ' MoveShip

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

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

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

Sub ShowScore
    If m_bCocktailMode = TRUE Then
        ShowScoreCocktail
    Else
        ShowScoreNormal
    End If
End Sub ' ShowScore

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

Sub ShowScoreNormal
    Dim iStartRow As Integer
    Dim iStartCol As Integer
    Dim iRow As Integer
    Dim iCol As Integer
    Dim iCount As Integer
    Dim iShip1 As Integer
    Dim iPlayer As Integer
    Dim sWhich As String
    Dim iTotalRows As Integer: iTotalRows = _Height(0) \ _FontHeight
    Dim iTotalCols As Integer: iTotalCols = _Width(0) \ _FontWidth
   
    Color cBlue, cBlue: PrintString1 iTotalRows, 0, String$(iTotalCols, " ")
    Color cWhite, cEmpty: PrintString1 iTotalRows, 0, "Level: " + cstr$(m_iLevel)
    PrintString1 iTotalRows, iTotalCols - 20, "Press Esc to quit"
   
    iRow = 1
    iCol = 1
    iCount = 0
    For iShip1 = 1 To m_iNumShips
        Color m_arrShip(iShip1).BodyColor, cEmpty
        PrintString1 iRow, iCol, _
            m_arrShip(iShip1).name + " " + _
            cstr$(m_arrShip(iShip1).score) + " " + _
            cstr$(m_arrShip(iShip1).shields)
        iCount = iCount + 1
       
        'A 999 (999)[10]{99} B (999)[10]{99} C (999)[10]{99} D (999)[10]{99} E (999)[10]{99} F (999)[10]{99} G (999)[10]{99} H (999)[10]{99}
        '12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678
        'I (999)[10]{99} J (999)[10]{99} K (999)[10]{99} M (999)[10]{99} N (999)[10]{99} O (999)[10]{99} P (999)[10]{99} Q (999)[10]{99}
       
       
        iRow = iRow + 2
    Next iShip1
   
    For iPlayer = 1 To m_iPlayers
        If m_arrPlayer(iPlayer).WhichShip < 0 Then
            sWhich = "None (all destroyed)"
        ElseIf m_arrPlayer(iPlayer).WhichShip = 0 Then
            sWhich = "All"
        Else
            sWhich = LeftPadString$(cstr$(m_arrPlayer(iPlayer).WhichShip), 2, " ")
        End If
        Color cWhite, cEmpty
        PrintString iRow, iCol, _
            "Player #" + _
            LeftPadString$(cstr$(iPlayer), 2, " ") + _
            " controls " + _
            sWhich
       
        iRow = iRow + 2
    Next iPlayer
   
    ' SHOW INSTRUCTIONS
   
End Sub ' ShowScoreNormal

Sub ShowScoreCocktail

End Sub ' ShowScoreCocktail

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

Sub ShowScore1
    Dim iStartRow As Integer
    Dim iStartCol As Integer
    Dim iRow As Integer
    Dim iCol As Integer
    Dim iCount As Integer
    Dim iShip1 As Integer
    Dim iPlayer As Integer
    Dim sWhich As String
   
    ' Draw background
    Color cBlue, cBlue
    PrintString 0, 0, String$(120, " ")
   
    ' Place the text on the form
   
    'TODO: display score for all players
    'Color cLime, cEmpty
    'PrintString 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
    'PrintString 0, 40, "Score: " + cstr$(m_iScore) ' LeftPadString$(cstr$(m_iScore), 10, " ")
   
    ' Display the level
    Color cWhite, cEmpty
    PrintString 0, 70, "Level: " + cstr$(m_iLevel) ' LeftPadString$(cstr$(m_iScore), 10, " ")
   
   
    iRow = 0
    iCol = 0
    iCount = 0
    For iShip1 = 1 To m_iNumShips
        Color m_arrShip(iShip1).BodyColor, cEmpty
        PrintString iRow, iCol, _
            "Player #" + _
            LeftPadString$(cstr$(iShip1), 2, " ") + _
            " (" + m_arrShip(iShip1).name + ") : " + _
            "Shields: " + LeftPadString$(cstr$(m_arrShip(iShip1).shields), 5, " ") + "  " + _
            "Score: " + LeftPadString$(cstr$(m_arrShip(iShip1).score), 5, " ") + "  " + _
            "x: " + LeftPadString$(_Trim$(Str$(m_arrShip(iShip1).xPos)), 5, " ") + "  " + _
            "y: " + LeftPadString$(_Trim$(Str$(m_arrShip(iShip1).yPos)), 5, " ") + "  " + _
            "dx: " + LeftPadString$(SngRoundedToStr$(m_arrShip(iShip1).dx, 5), 5, " ") + "  " + _
            "dy: " + LeftPadString$(SngRoundedToStr$(m_arrShip(iShip1).dy, 5), 5, " ") + "  " + _
            "vx: " + LeftPadString$(SngRoundedToStr$(m_arrShip(iShip1).vx, 5), 5, " ") + "  " + _
            "vy: " + LeftPadString$(SngRoundedToStr$(m_arrShip(iShip1).vy, 5), 5, " ") + "  " + _
            IIFSTR$ ( m_arrShip(iShip1).Left_IsPressed, "LEFT ", "") + _
            IIFSTR$ ( m_arrShip(iShip1).Right_IsPressed, "RIGHT ", "") + _
            IIFSTR$ ( m_arrShip(iShip1).Up_IsPressed, "UP ", "") + _
            IIFSTR$ ( m_arrShip(iShip1).Down_IsPressed, "DOWN ", "") + _
            IIFSTR$ ( m_arrShip(iShip1).Button1_IsPressed, "BUTTON #1 ", "") + _
            IIFSTR$ ( m_arrShip(iShip1).Button2_IsPressed, "BUTTON #2 ", "") + _
            ""
        iRow = iRow + 2
    Next iShip1
   
    For iPlayer = 1 To m_iPlayers
        If m_arrPlayer(iPlayer).WhichShip < 0 Then
            sWhich = "None (all destroyed)"
        ElseIf m_arrPlayer(iPlayer).WhichShip = 0 Then
            sWhich = "All"
        Else
            sWhich = LeftPadString$(cstr$(m_arrPlayer(iPlayer).WhichShip), 2, " ")
        End If
        Color cWhite, cEmpty
        PrintString iRow, iCol, _
            "Player #" + _
            LeftPadString$(cstr$(iPlayer), 2, " ") + _
            " controls " + _
            sWhich
       
        iRow = iRow + 2
    Next iPlayer
   
    'Color cPurple
    ''PrintString 3, 0, "Facing: " + SngRoundedToStr$(m_arrShip(iLoop1).facing, 5)
    'PrintString 3, 0, "     dx: " + SngRoundedToStr$(m_arrShip(iLoop1).dx, 5)
    'PrintString 4, 0, "     vx: " + SngRoundedToStr$(m_arrShip(iLoop1).vx, 5)
    'PrintString 5, 0, "m_arrDX: " + SngRoundedToStr$(m_arrDX(m_arrShip(iLoop1).dx), 5)
    '
    'PrintString 7, 0, "    dy: " + SngRoundedToStr$(m_arrShip(iLoop1).dy, 5)
    'PrintString 8, 0, "    vy: " + SngRoundedToStr$(m_arrShip(iLoop1).vy, 5)
    'PrintString 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
    'PrintString 45, 0, String$(120, " ")
    'Color cWhite, cEmpty
    'PrintString 45, 0, "CONTROLS: LEFT/RIGHT = TURN   UP/DOWN = FORWARD/BACK   CTRL=FIRE   1=ADD SHIELD (CHEAT)"
   
End Sub ' ShowScore1

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

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

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

Sub DrawShip (iShip1 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(iShip1).xPos + SHIP_RADIUS * Sin(m_arrShip(iShip1).facing)
    intY1 = m_arrShip(iShip1).yPos - SHIP_RADIUS * Cos(m_arrShip(iShip1).facing)
   
    ' left rear
    intX2 = m_arrShip(iShip1).xPos + SHIP_RADIUS * Sin(m_arrShip(iShip1).facing + 2 * PI / 3)
    intY2 = m_arrShip(iShip1).yPos - SHIP_RADIUS * Cos(m_arrShip(iShip1).facing + 2 * PI / 3)
   
    ' left rear (3/4 of the way down)
    intX2b = m_arrShip(iShip1).xPos + ENGINE_RADIUS * Sin(m_arrShip(iShip1).facing + 2 * PI / 3)
    intY2b = m_arrShip(iShip1).yPos - ENGINE_RADIUS * Cos(m_arrShip(iShip1).facing + 2 * PI / 3)
   
    ' right rear
    intX3 = m_arrShip(iShip1).xPos + SHIP_RADIUS * Sin(m_arrShip(iShip1).facing + 4 * PI / 3)
    intY3 = m_arrShip(iShip1).yPos - SHIP_RADIUS * Cos(m_arrShip(iShip1).facing + 4 * PI / 3)

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

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

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

Sub CheckForEnd ()
    Dim iPlayersLeft As Integer
    Dim iEnemiesLeft As Integer
    Dim iShip1 As Integer
    Dim iEnemy1 As Integer
    Dim bRespawn As Integer: bRespawn = FALSE
    Dim bAdvance As Integer: bAdvance = FALSE
   
    ' HOW MANY ENEMIES STILL ALIVE?
    iEnemiesLeft = 0
    For iEnemy1 = 0 To UBound(m_arrEnemy)
        If m_arrEnemy(iEnemy1).alive = TRUE Then
            iEnemiesLeft = iEnemiesLeft + 1
        End If
    Next iEnemy1
   
    ' HOW MANY PLAYERS STILL ALIVE?
    iPlayersLeft = 0
    For iShip1 = 1 To m_iNumShips
        If m_arrShip(iShip1).shields > 0 Then
            iPlayersLeft = iPlayersLeft + 1
        End If
    Next iShip1
   
    ' IS GAME OVER?
    If m_iNumShips = 1 Then
        ' SINGLE PLAYER GAME
        If iPlayersLeft < 1 Then
            m_bGameOver = TRUE
        Else
            ' DOES THIS GAME HAVE ENEMIES?
            If m_bSimpleEnemies = TRUE Then
                ' ADVANCE IF ALL ENEMIES DEAD
                If iEnemiesLeft = 0 Then
                    bAdvance = TRUE
                    bRespawn = TRUE
                End If
            End If
        End If
    Else
        ' MULTIPLAYER GAME
        If iPlayersLeft < 2 Then
            ' LESS THAN 2 PLAYERS LEFT, ADVANCE
            bAdvance = TRUE
        Else
            ' MORE THAN ONE PLAYER LEFT...
           
            ' DOES THIS GAME HAVE ENEMIES?
            If m_bSimpleEnemies = TRUE Then
                ' ADVANCE IF ALL ENEMIES DEAD
                If iEnemiesLeft = 0 Then
                    ' NEXT LEVEL (RESPAWN ENEMIES)
                    bRespawn = TRUE
                End If
            End If
        End If
    End If
   
    ' ADVANCE TO NEXT ROUND
    If m_bGameOver = FALSE Then
        If bAdvance = TRUE Then
            If m_iRoundsPerGame > 0 Then
                m_iLevel = m_iLevel + 1
               
                ' HAVE WE FINISHED THE FINAL ROUND?
                If m_iLevel > m_iRoundsPerGame Then
                    m_bGameOver = TRUE
                End If
            Else
                ' INFINITE ROUNDS (STOP COUNTING AFTER 25)
                If m_iLevel < 255 Then
                    m_iLevel = m_iLevel + 1
                End If
            End If
        End If
    End If
   
    ' ADVANCE OR RESPAWN
    If m_bGameOver = FALSE Then
        If bAdvance = TRUE Then
            StartNextRound
        End If
        If bRespawn = TRUE Then
            Respawn
        End If
    End If
   
End Sub ' CheckForEnd

' /////////////////////////////////////////////////////////////////////////////
' Reset players for new round

Sub StartNextRound ()
    Dim iShip1 As Integer
    Dim iShip2 As Integer
    Dim iPlayer As Integer
    Dim iLoop1 As Integer
    Dim iMinShip As Integer
    Dim iMaxShip As Integer
    Dim iTurn As Integer
    Dim bDone As Integer
   
    ' Initialize round
    If m_iPlayers > 0 Then
        ' Game status
        m_bGameOver = FALSE
       
        ' MAP PLAYERS TO SHIPS AND COUNT TOTAL # OF SHIPS
        m_iNumShips = 0
        For iPlayer = 1 To m_iPlayers
            iMinShip = 0
            iMaxShip = 0
           
            For iShip1 = 1 To m_iShipsPerPlayer
                ' GET SHIP #
                m_iNumShips = m_iNumShips + 1
               
                ' ASSIGN SHIP TO PLAYER
                m_arrShip(m_iNumShips).PlayerNum = iPlayer
               
                ' SAVE FIRST & LAST SHIP INDEX FOR CURRENT PLAYER
                If iMinShip = 0 Then
                    iMinShip = m_iNumShips
                End If
                If m_iNumShips > iMaxShip Then
                    iMaxShip = m_iNumShips
                End If
            Next iShip1
           
            ' INITIALIZE PLAYER FIRST/LAST/CURRENT SHIP
            m_arrPlayer(iPlayer).FirstShip = iMinShip
            m_arrPlayer(iPlayer).LastShip = iMaxShip
            m_arrPlayer(iPlayer).WhichShip = iMinShip
           
            ' RESET BUTTON #2 CONTROL STATUS
            m_arrPlayer(iPlayer).Button2_IsReady = TRUE
        Next iPlayer
       
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        ' END WHO CONTROLS WHAT
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
       
        ' CLEAR CONTROL BUFFER FOR SHIPS
        For iShip1 = 1 To 16
            ClearInputBuffer iShip1
        Next iShip1
       
        ' Set the starting positions of the ships
        For iShip1 = 1 To m_iNumShips
            ' EVERYONE STARTS WITH SAME SHIELDS
            ' TODO: GET THIS VALUE FROM OPTIONS
            m_arrShip(iShip1).shields = NUM_SHIELDS
           
            ' TODO: ADD OPTIONS FOR FUEL AND AMMO, FOR NOW NOT USED...
            m_arrShip(iShip1).fuel = 100
            If m_iNumShips > 1 Then
                m_arrShip(iShip1).ammo = m_iNumShips * 2 ' 2 shots per opponent
            Else
                m_arrShip(iShip1).ammo = 10
            End If

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

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

            ' TODO: REMOVE UNUSED VARIABLES
            m_arrShip(iShip1).heading = 0
            m_arrShip(iShip1).facing = 0
            m_arrShip(iShip1).speed = 0
        Next iShip1
       
        ' RESET BULLETS
        ReDim _Preserve m_arrBullet(-1) As BulletType
       
    End If
   
End Sub ' StartNextRound

' /////////////////////////////////////////////////////////////////////////////
' Respawn the enemies, increase # and difficulty

Sub Respawn ()
    Dim iEnemy1 As Integer
    Dim iShip1 As Integer
    Dim bDone As Integer
   
    ' 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 iShip1 = 1 To m_iNumShips
        If m_arrShip(iShip1).shields > 0 Then
            m_arrShip(iShip1).shields = m_arrShip(iShip1).shields + BONUS_SHIELDS
            m_arrShip(iShip1).level = m_arrShip(iShip1).level + 1
            m_arrShip(iShip1).score = m_arrShip(iShip1).score + 100
        End If
    Next iShip1
   
    ' 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 iEnemy1 = 0 To UBound(m_arrEnemy)
        ' choose a random size
        m_arrEnemy(iEnemy1).radius = RandomNumber%(m_iMinEnemyRadius, m_iMaxEnemyRadius)
       
        '' Set the starting positions
        'm_arrEnemy(iEnemy1).xPos = m_arrShip(iEnemy1).xPos
        'm_arrEnemy(iEnemy1).yPos = m_arrShip(iEnemy1).yPos
       
        ' give enemy life
        m_arrEnemy(iEnemy1).alive = TRUE
        'm_arrEnemy(iEnemy1).life = 30
        m_arrEnemy(iEnemy1).life = m_arrEnemy(iEnemy1).radius
        m_arrEnemy(iEnemy1).maxlife = m_arrEnemy(iEnemy1).radius
       
        ' make sure the enemies don't start on top of any ships
        bDone = FALSE
        Do
            m_arrEnemy(iEnemy1).xPos = RandomNumber%(iMinX, iMaxX) 'Int(Rnd * ScaleWidth + 1)
            m_arrEnemy(iEnemy1).yPos = RandomNumber%(iMinY, iMaxY) 'Int(Rnd * ScaleHeight + 1)
           
            For iShip1 = 1 To m_iNumShips
                If m_arrShip(iShip1).shields > 0 Then
                    If GetDist(m_arrEnemy(iEnemy1).xPos, m_arrEnemy(iEnemy1).yPos, m_arrShip(iShip1).xPos, m_arrShip(iShip1).yPos) > SHIP_RADIUS * 10 Then
                        bDone = TRUE
                        Exit For
                    End If
                End If
            Next iShip1
           
            If bDone Then Exit Do
        Loop
       
    Next iEnemy1
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 SIMPLE LINE COLLISION DETECTION #COLLISION1
' ################################################################################################################################################################

' /////////////////////////////////////////////////////////////////////////////
' Simple test for line intersection

' It might be useful for something (Reply #6)
' https://qb64phoenix.com/forum/showthread.php?tid=2134&pid=29938

' From: MasterGy
' Date: 11-01-2023, 05:14 AM (This post was last modified: 11-01-2023, 05:18 AM by MasterGy.)

Function IsIntersection (X1, Y1, X2, Y2, X3, Y3, X4, Y4)
    Denominator = (Y4 - Y3) * (X2 - X1) - (X4 - X3) * (Y2 - Y1)
    If Denominator = 0 Then
        IsIntersection = 0
    Else
        Ua = ((X4 - X3) * (Y1 - Y3) - (Y4 - Y3) * (X1 - X3)) / Denominator
        Ub = ((X2 - X1) * (Y1 - Y3) - (Y2 - Y1) * (X1 - X3)) / Denominator
        If Ua >= 0 And Ua <= 1 And Ub >= 0 And Ub <= 1 Then IsIntersection = 1 Else IsIntersection = 0
    End If
End Function ' IsIntersection

' /////////////////////////////////////////////////////////////////////////////
' Test / demo of IsIntersection function by MasterGy
' https://qb64phoenix.com/forum/showthread.php?tid=2134&pid=29938

' From: MasterGy
' Date: 11-01-2023, 05:14 AM (This post was last modified: 11-01-2023, 05:18 AM by MasterGy.)

Sub IsIntersectionTest
    Dim x1, y1, x2, y2, x3, y3, x4, y4
    Screen _NewImage(800, 600, 32)
    Do
        Cls
       
        x1 = 800 * Rnd
        y1 = 600 * Rnd
       
        x2 = 800 * Rnd
        y2 = 600 * Rnd
       
        x3 = 800 * Rnd
        y3 = 600 * Rnd
       
        x4 = 800 * Rnd
        y4 = 600 * Rnd
       
        Color _RGB32(255, 255, 255)
        Locate 1, 1: Print "SPACE to next"
       
        If IsIntersection(x1, y1, x2, y2, x3, y3, x4, y4) Then
            Color _RGB32(255, 50, 50)
            Locate 3, 10: Print "Intersect !"
        End If
       
        Line (x1, y1)-(x2, y2)
        Line (x3, y3)-(x4, y4)
       
        Do: Loop Until InKey$ = " "
    Loop
End Sub ' IsIntersectionTest

' ################################################################################################################################################################
' END SIMPLE LINE COLLISION DETECTION #COLLISION1
' ################################################################################################################################################################

' ################################################################################################################################################################
' BEGIN COMPLEX LINE COLLISION DETECTION #COLLISION2
' ################################################################################################################################################################

' It might be useful for something (Reply #6)
' https://qb64phoenix.com/forum/showthread.php?tid=2134&pid=29992#pid29992

' From: bplus, Mini-Mod
' Date: 11-21-2024, 05:13 PM
'
' >>(11-01-2023, 08:20 AM) bplus Wrote:
' >>    Good stuff! let me know if you want exact point (coordinates) of intersect,
' >>    though my code is a little longer Smile
' >(11-21-2024, 01:52 PM) madscijr Wrote:
' >     I could use that...
'
' WTH? I lost all the reply I was working on.
' Here's the code, forget the intro:

' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
' Just worked Rosetta Code for Line Intersect Line
' but what if we want to know if two line segments intersect?

' DATE         WHO-DONE-IT   DID-WHAT
' 2020-03-14   b+            Two Line Segments Intersect, start
' 2020-03-15   b+            rework this code so we identify points all on same line
'                            and if there is overlap of line segments say the two x endpoints of the segments
'                            otherwise, if there is an intersect of 2 line segments say the point x, y.
'                            Return 0 no intersect or overlap
'                            Return 1 if intersect and ix, iy point of intersect
'                            Return -1 if segments are on same and there is overlap: ix = overlap start x, iy overlap end x
' 2020-03-16   b+            "Segments Intersect mod tester"  >>> just post testing code
'                            mod tester for 2 segments of vertical line and found I need to add more parameters to
'                            FUNCTION twoLineSegmentsIntersect%  (ax1, ay1, ax2, ay2, bx1, by1, bx2, by2, ix, iy)
'                            mod that name and parameters to:
'                            FUNCTION TwoSegmentsIntersect%  (ax1, ay1, ax2, ay2, bx1, by1, bx2, by2, ix1, iy1, ix2, iy2)
' 2020-03-16   b+            Segments Intersect revised 2020-03-16
'                            OK now get the new FUNCTION working
'                            ah! I had to tighten down D from >.2 to >.05 but loosen y-axis intersect
' 2025-01-05   madscijr      Reformatted bplus's code to match coding style for use in Multispacewar! game.
' ----------------------------------------------------------------------------------------------------------------------------------------------------------------

' /////////////////////////////////////////////////////////////////////////////
' This function needs: SUB SlopeYintersect (X1, Y1, X2, Y2, slope, Yintercept)
' Receives:
'     ax1, ay1, ax2, ay2 = start/end points of line #1
'     bx1, by1, bx2, by2 = start/end points of line #2
' Returns by reference:
'     ix, iy = if lines intersect, coordinates where they intersect
' Returns -1 (TRUE) if lines intersect or 0 (FALSE) if they don't.

Function LineIntersectLine% (ax1, ay1, ax2, ay2, bx1, by1, bx2, by2, ix, iy)
    If ax1 = ax2 Then 'line a is vertical
        If bx1 = bx2 Then ' b is vertical
            If ax1 = bx1 Then LineIntersectLine% = -1 ' if x's are same it is same vertical line
            Exit Function '
        Else
            ix = ax1
            SlopeYintersect bx1, by1, bx2, by2, m2, y02
            iy = m2 * ix + y02
            LineIntersectLine% = 1 ' signal a point was found
            Exit Function
        End If
    Else
        SlopeYintersect ax1, ay1, ax2, ay2, m1, y01 ' -m = a, 1 = b, y0 = c  std form
    End If
    If bx1 = bx2 Then 'b is vertical
        ix = bx1: iy = m1 * ix + y01: LineIntersectLine% = 1 'signal a point was found
        Exit Function
    Else
        SlopeYintersect bx1, by1, bx2, by2, m2, y02 ' -m = a, 1 = b, y0 = c  std form
    End If
    d = -m1 - -m2 ' if = 0 then parallel or equal because slopes are same
    If Abs(d) > .05 Then 'otherwise about 0 <<< tighten down from .2 to .05
        ix = (y01 - y02) / d: iy = (-m1 * y02 - -m2 * y01) / d
        LineIntersectLine% = 1 'signal one intersect point was found
    Else 'same line or parallel? if y0 (y-axis interssect) are same they are the same
        If Abs(y01 - y02) < 15 Then LineIntersectLine% = -1 'signal same line!  <<< loosen more! 5 to 15
    End If
End Function ' LineIntersectLine%

' /////////////////////////////////////////////////////////////////////////////
' Slope and Y-intersect for non vertical lines,
' if x1 = x2 the line is vertical don't call this sub
' because slope calculation would cause division by 0 error.

Sub SlopeYintersect (X1, Y1, X2, Y2, slope, Yintercept) 'check x1 <> x2 first!
    slope = (Y2 - Y1) / (X2 - X1): Yintercept = slope * (0 - X1) + Y1
End Sub ' SlopeYintersect

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

Function ts$ (n)
    ts$ = _Trim$(Str$(Int(100 * n) / 100))
End Function ' ts$

' /////////////////////////////////////////////////////////////////////////////
' This function needs: FUNCTION LineIntersectLine% (ax1, ay1, ax2, ay2, bx1, by1, bx2, by2, ix, iy)
' which in turn needs: SUB SlopeYintersect (X1, Y1, X2, Y2, slope, Yintercept) 'check x1 <> x2 first!

' Receives:
'     ax1, ay1, ax2, ay2 = start/end points of line #1
'     bx1, by1, bx2, by2 = start/end points of line #2
' Returns by reference:
'     ix1, iy1 = if lines intersect, coordinates where they intersect
'                if lines overlap, coordinates where overlap begins
'     ix2, iy2 = if lines overlap, coordinates where overlap ends
' Returns -1 (TRUE) if lines intersect or 0 (FALSE) if they don't.

Function TwoSegmentsIntersect% (ax1, ay1, ax2, ay2, bx1, by1, bx2, by2, ix1, iy1, ix2, iy2)
    intersect = LineIntersectLine%(ax1, ay1, ax2, ay2, bx1, by1, bx2, by2, ix, iy)
    If ax1 < ax2 Then aMinX = ax1: aMaxX = ax2 Else aMinX = ax2: aMaxX = ax1
    If ay1 < ay2 Then aMinY = ay1: aMaxY = ay2 Else aMinY = ay2: aMaxY = ay1
    If bx1 < bx2 Then bMinX = bx1: bMaxX = bx2 Else bMinX = bx2: bMaxX = bx1
    If by1 < by2 Then bMinY = by1: bMaxY = by2 Else bMinY = by2: bMaxY = by1
    If intersect = 0 Then 'no  intersect
        TwoSegmentsIntersect% = 0
    ElseIf intersect = 1 Then 'segments intersect at one point
        If ax1 = ax2 Then 'is iy between
            If iy < aMinY Or iy > aMaxY Or ix < bMinX Or ix > bMaxX Then
                TwoSegmentsIntersect% = 0
            Else
                ix1 = ix: iy1 = iy: TwoSegmentsIntersect% = 1
            End If
        ElseIf bx1 = bx2 Then
            If iy < bMinY Or iy > bMaxY Or ix < aMinX Or ix > aMaxX Then
                TwoSegmentsIntersect% = 0
            Else
                ix1 = ix: iy1 = iy: TwoSegmentsIntersect% = 1
            End If
        Else
            If (aMinX <= ix And ix <= aMaxX) And (bMinX <= ix And ix <= bMaxX) Then
                ix1 = ix: iy1 = iy: TwoSegmentsIntersect% = 1
            Else
                TwoSegmentsIntersect% = 0
            End If
        End If
    ElseIf intersect = -1 Then 'segments are on same line get over lap section
        'first check if both are on vertical line
        If ax1 = ax2 Then 'and we know both are same line  we have two vertical segemnts, do they over lap?
            ix1 = ax1: ix2 = ax1
            If aMinY < bMinY Then
                If aMaxY < bMinY Then
                    TwoSegmentsIntersect% = 0
                Else
                    TwoSegmentsIntersect% = -1: iy1 = bMinY
                    If aMaxY > bMaxY Then
                        iy2 = bMaxY
                    Else
                        iy2 = aMaxY
                    End If
                End If
            Else 'bMinY <= aMinY
                If bMaxY < aMinY Then
                    TwoSegmentsIntersect% = 0
                Else
                    TwoSegmentsIntersect% = -1: iy1 = aMinY
                    If bMaxY > aMaxY Then
                        iy2 = aMaxY
                    Else
                        iy2 = bMaxY
                    End If
                End If
            End If
        Else 'the same line is not vertical
            If aMinX < bMinX Then
                If aMaxX < bMinX Then
                    TwoSegmentsIntersect% = 0
                Else
                    TwoSegmentsIntersect% = -1: ix1 = bMinX
                    If bx1 = bMinX Then iy1 = by1 Else iy1 = by2
                    If aMaxX > bMaxX Then
                        ix2 = bMaxX
                        If bx1 = bMaxX Then iy2 = by1 Else iy2 = by2
                    Else
                        ix2 = aMaxX
                        If ax1 = aMaxX Then iy2 = ay1 Else iy2 = ay2
                    End If
                End If
            Else 'aMinX >= bMinX
                If aMinX > bMaxX Then
                    TwoSegmentsIntersect% = 0
                Else
                    TwoSegmentsIntersect% = -1: ix1 = aMinX
                    If ax1 = aMinX Then iy1 = ay1 Else iy1 = ay2
                    If bMaxX > aMaxX Then
                        ix2 = aMaxX
                        If ax1 = aMaxX Then iy2 = ay1 Else iy2 = ay2
                    Else
                        ix2 = bMaxX
                        If bx1 = bMaxX Then iy2 = by1 Else iy2 = by2
                    End If
                End If
            End If
        End If
    End If
End Function ' TwoSegmentsIntersect

' /////////////////////////////////////////////////////////////////////////////
' Test / demo of LineIntersectLine and TwoSegmentsIntersect functions.

Sub LineIntersectTest
    Const xmax = 1200, ymax = 700
    Dim ax1 As Integer, ax2 As Integer, ay1 As Integer, ay2 As Integer
    Dim bx1 As Integer, bx2 As Integer, by1 As Integer, by2 As Integer
   
    Screen _NewImage(xmax, ymax, 32)
    _Delay .25
    _ScreenMove _Middle
   
    Do
        restartA:
        Cls
        If Rnd < .3 Then 'throw in some vertical lines
            Locate 3, 80: Print "Red Points are vertical."
            ax1 = (xmax - 20) * Rnd + 10: ay1 = (ymax - 60) * Rnd + 50
            ax2 = ax1: ay2 = (ymax - 60) * Rnd + 50
        Else
            Locate 3, 80: Print "Red Points are Random."
            ax1 = (xmax - 20) * Rnd + 10: ay1 = (ymax - 60) * Rnd + 50
            ax2 = (xmax - 20) * Rnd + 10: ay2 = (ymax - 60) * Rnd + 50
        End If
        If _Hypot(ax1 - ax2, ay1 - ay2) < 50 Then GoTo restartA

        If Rnd < .6 Then 'get some points on same line
            Locate 3, 80: Print "Blue Points are on same line as Red."
            SlopeYintersect ax1, ay1, ax2, ay2, slope1, Yintercept1
            bx1 = (xmax - 20) * Rnd + 10: by1 = bx1 * slope1 + Yintercept1
            bx2 = (xmax - 20) * Rnd + 10: by2 = bx2 * slope1 + Yintercept1
        Else
            If Rnd < .4 Then 'throw in some verticals, we already have a doing verticals
                Locate 3, 80: Print Space$(50)
                Locate 3, 80: Print "All points vertical."
                ax1 = (xmax - 20) * Rnd + 10: ax2 = ax1: bx1 = ax1: bx2 = ax1
                ay1 = 50 + Rnd * 50: ay2 = ay1 + 50 + Rnd * 50
                by1 = ay1 + 25 + Rnd * 50: by2 = by1 + 50 + (Rnd * ymax - 60 - by1)
                by1 = (ymax - 60) * Rnd + 50: bx2 = bx1: by2 = (ymax - 60) * Rnd + 50
            Else
                Locate 4, 80: Print "Blue Points are Random."
                bx1 = (xmax - 20) * Rnd + 10: by1 = (ymax - 60) * Rnd + 50
                bx2 = (xmax - 20) * Rnd + 10: by2 = (ymax - 60) * Rnd + 50
            End If
        End If
       
        If bx1 < 10 Or bx1 > xmax - 10 Then GoTo restartA
        If bx2 < 10 Or bx2 > xmax - 10 Then GoTo restartA
        If by1 < 50 Or by1 > ymax - 10 Then GoTo restartA
        If by2 < 50 Or by2 > ymax - 10 Then GoTo restartA
        If _Hypot(bx1 - bx2, by1 - by2) < 30 Then GoTo restartA
       
        Line (ax1, ay1)-(ax2, ay2), &HFFFF0000
        Circle (ax1, ay1), 4, &HFFFF0000
        Circle (ax2, ay2), 4, &HFFFF0000
       
        Line (bx1, by1)-(bx2, by2), &HFF0000FF
        Circle (bx1, by1), 4, &HFF0000FF
        Circle (bx2, by2), 4, &HFF0000FF
       
        Locate 1, 1
        PRINT "Segments ("; ts$(ax1); ", "; ts$(ay1); ") ("; ts$(ax2); ", "; _
            ts$(ay2); ") and ("; ts$(bx1); ", "; ts$(by1); ") ("; ts$(bx2); ", "; ts$(by2); ")"
       
        '                    Plug in your 2 Segment Intersect SUB or FUNCTION Here
        '                 and interpret reults: yellow circle around intersect point
        '                and an alpha shaded box where two co-linear segments overlap
        '=====================================================================================================
        intersect = TwoSegmentsIntersect%(ax1, ay1, ax2, ay2, bx1, by1, bx2, by2, ix1, iy1, ix2, iy2)
        If intersect = -1 Then 'segments overlap on same line
            Print " Segments overlap between: ("; ts$(ix1); ", "; ts$(iy1); ") and ("; ts$(ix2); ", "; ts$(iy2); ")"
            Line (ix1, iy1)-(ix2, iy2), &HFFFFFFFF
        ElseIf intersect = 1 Then 'segments intersect at one point
            Print " Segments intersect: ("; ts$(ix1); ", "; ts$(iy1); ")"
            Circle (ix1, iy1), 3, &HFFFFFFFF
        ElseIf intersect = 0 Then 'segments do not intersect nor overlap
            Print " Segments do not Intersect or Overlap."
        End If
        '=====================================================================================================
       
        Input "Press enter for another demo, any + enter to quit...", again$
        Cls
    Loop Until Len(again$)
End Sub ' LineIntersectTest

' ################################################################################################################################################################
' END COMPLEX LINE COLLISION DETECTION #COLLISION2
' ################################################################################################################################################################

' ################################################################################################################################################################
' 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 COLOR ARRAY FUNCTIONS #COLR
' ################################################################################################################################################################

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

Sub AddColor (ColorValue As _Unsigned Long, arrColor() As _Unsigned Long)
    ReDim _Preserve arrColor(0 To UBound(arrColor) + 1) As _Unsigned Long
    arrColor(UBound(arrColor)) = ColorValue
End Sub ' AddColor

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

Sub AddColors (ColorValue As _Unsigned Long, arrColor() As _Unsigned Long, HowMany As Long)
    Dim iLoop As Integer
    For iLoop = 1 To HowMany
        AddColor ColorValue, arrColor()
    Next iLoop
End Sub ' AddColors

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

Sub AddGrayscaleColors (arrColor() As _Unsigned Long)
    Dim iNum As Integer
    iNum = 1
    AddColors cDimGray, arrColor(), iNum
    AddColors cGray, arrColor(), iNum
    AddColors cDarkGray, arrColor(), iNum
    AddColors cSilver, arrColor(), iNum
    AddColors cLightGray, arrColor(), iNum
    AddColors cGainsboro, arrColor(), iNum
    AddColors cWhiteSmoke, arrColor(), iNum
    AddColors cWhite, arrColor(), iNum '* 2
    AddColors cWhiteSmoke, arrColor(), iNum
    AddColors cGainsboro, arrColor(), iNum
    AddColors cLightGray, arrColor(), iNum
    AddColors cSilver, arrColor(), iNum
    AddColors cDarkGray, arrColor(), iNum
    AddColors cGray, arrColor(), iNum
End Sub ' AddGrayscaleColors

' ################################################################################################################################################################
' END COLOR ARRAY FUNCTIONS @COLR
' ################################################################################################################################################################

' ################################################################################################################################################################
' 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)
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 = _RGB32(0, 0, 0, 0)
End Function ' cEmpty~&

' ################################################################################################################################################################
' END COLOR FUNCTIONS @COLOR
' ################################################################################################################################################################

' ################################################################################################################################################################
' BEGIN KEYBOARD CODES 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 CODES FUNCTIONS @KEYCODE
' ################################################################################################################################################################

' ################################################################################################################################################################
' 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$

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

$If  Then
    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$
$End If

' /////////////////////////////////////////////////////////////////////////////
' 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

' /////////////////////////////////////////////////////////////////////////////
' 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$

' /////////////////////////////////////////////////////////////////////////////
' 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

' /////////////////////////////////////////////////////////////////////////////
' 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

' /////////////////////////////////////////////////////////////////////////////
' Draws scaled + rotated text to screen
' by BPlus

' Scale rotate font text strings by B+
' https://qb64phoenix.com/forum/showthread.php?tid=414&highlight=rotate+text

' INPUT:
' S$ is the string to display
' c is the color (will have a transparent background)
' midX and midY is the center of where you want to display the string
' xScale would multiply 8 pixel width of default font
' yScale would multiply the 16 pixel height of the default font
' Rotation is in Radian units, use _D2R to convert Degree units to Radian units

' DEPENDENCIES:
' drwString needs sub RotoZoom2, intended for graphics screens using the default font.

Sub drwString (S$, c As _Unsigned Long, midX, midY, xScale, yScale, Rotation As Single)
    I& = _NewImage(_PrintWidth(S$), _FontHeight, 32)
    _Dest I&
    Color c, _RGBA32(0, 0, 0, 0)
    _PrintString (0, 0), S$
    _Dest storeDest&
    RotoZoom2 midX, midY, I&, xScale, yScale, Rotation
    _FreeImage I&
End Sub ' drwString

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

Sub DumpScreenAndFontSize ()
    Dim iCols As Integer
    Dim iRows As Integer
    'Screen _NewImage(1024, 768, 32): _ScreenMove 0, 0
    iCols = _Width(0) \ _FontWidth
    iRows = _Height(0) \ _FontHeight
    Print "_Width(0)  =" + _Trim$(Str$(_Width(0)))
    Print "_Height(0) =" + _Trim$(Str$(_Height(0)))
    Print "_FontWidth =" + _Trim$(Str$(_FontWidth))
    Print "_FontHeight=" + _Trim$(Str$(_FontHeight))
    Print "iCols = _Width(0)  \ _FontWidth  = " + _Trim$(Str$(iCols))
    Print "iRows = _Height(0) \ _FontHeight = " + _Trim$(Str$(iRows))
End Sub ' DumpScreenAndFontSize

' /////////////////////////////////////////////////////////////////////////////
' 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

' /////////////////////////////////////////////////////////////////////////////
' Gets the # of seconds so far today

Function GetTimeSeconds& ()
    Dim result&: result& = 0
    Dim sTime$: sTime$ = Time$
    Dim sHH24$: sHH24$ = Mid$(sTime$, 1, 2)
    Dim sMI$: sMI$ = Mid$(sTime$, 4, 2)
    Dim sSS$: sSS$ = Mid$(sTime$, 7, 2)
   
    result& = result& + Val(sSS$)
    result& = result& + (Val(sMI$) * 60)
    result& = result& + ((Val(sHH24$) * 60) * 60)
   
    ' RETURN RESULT
    GetTimeSeconds& = result&
End Function ' GetTimeSeconds&

' /////////////////////////////////////////////////////////////////////////////
' 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

' /////////////////////////////////////////////////////////////////////////////
' iNum% = PromptForIntegerInRange%("Please type a number between {min} and {max} (or blank to quit).", 1, 4, 0) ' prompt, min, max, default

Function PromptForIntegerInRange% (sPrompt$, iMin%, iMax%, iDefault%)
    Dim iValue%
    Dim bFinished%
    Dim sPrompt1$
    Dim in$

    If Len(sPrompt$) > 0 Then
        sPrompt1$ = sPrompt$
    Else
        sPrompt1$ = "Please type a number between {min} and {max} (or blank to quit)."
    End If

    sPrompt1$ = Replace$(sPrompt1$, "{min}", cstr$(iMin%))
    sPrompt1$ = Replace$(sPrompt1$, "{max}", cstr$(iMax%))

    bFinished% = FALSE
    Do
        Print sPrompt1$

        Input in$
        in$ = _Trim$(in$)
        If Len(in$) > 0 Then
            If IsNumber(in$) Then
                iValue% = Val(in$)
                If iValue% >= iMin% And iValue% <= iMax% Then
                    'bFinished% = TRUE
                    Exit Do
                Else
                    Print "Number out of range."
                    Print
                End If
            Else
                Print "Not a valid number."
                Print
            End If
        Else
            iValue% = iDefault%
            Exit Do
            'bFinished% = TRUE
        End If
    Loop Until bFinished% = TRUE

    PromptForIntegerInRange% = iValue%
End Function ' PromptForIntegerInRange%

' /////////////////////////////////////////////////////////////////////////////
' 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

' /////////////////////////////////////////////////////////////////////////////
' Initializes random number generator seed

' NOTE:
' using # of seconds so far in the day
' may be slightly more random than Randomize Timer
' unless user runs program at exact same time every day

Sub InitializeRandom
    Dim iSeed As Integer
   
    'iSeed = GetTimeSeconds& MOD 32767
   
    t9# = (Timer * 1000000) Mod 32767
   
    Randomize iSeed
    'print "Randomize " + cstr$(iSeed)
    'Sleep
End Sub ' InitializeRandom

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

' Note: random-number generator should be initialized with
'       InitializeRandom or Randomize Timer

Function RandomNumber% (Min%, Max%)
    Dim NumSpread%
    NumSpread% = (Max% - Min%) + 1
    RandomNumber% = Int(Rnd * NumSpread%) + Min% ' GET RANDOM # BETWEEN Max% AND Min%
End Function ' RandomNumber%

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

$If  Then
    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
$End If

' /////////////////////////////////////////////////////////////////////////////
' 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$

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

$If  Then
    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
$End If

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

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

' /////////////////////////////////////////////////////////////////////////////
' This sub gives really nice control over displaying an Image.
' by BPlus

' Scale rotate font text strings by B+
' https://qb64phoenix.com/forum/showthread.php?tid=414&highlight=rotate+text

' USED BY: drwString

Sub RotoZoom2 (centerX As Long, centerY As Long, Image As Long, xScale As Single, yScale As Single, Rotation As Single)
    Dim px(3) As Single: Dim py(3) As Single
    W& = _Width(Image&): H& = _Height(Image&)
    px(0) = -W& / 2: py(0) = -H& / 2: px(1) = -W& / 2: py(1) = H& / 2
    px(2) = W& / 2: py(2) = H& / 2: px(3) = W& / 2: py(3) = -H& / 2
    sinr! = Sin(-Rotation): cosr! = Cos(-Rotation)
    For i& = 0 To 3
        x2& = (px(i&) * cosr! + sinr! * py(i&)) * xScale + centerX: y2& = (py(i&) * cosr! - px(i&) * sinr!) * yScale + centerY
        px(i&) = x2&: py(i&) = y2&
    Next
    _MapTriangle (0, 0)-(0, H& - 1)-(W& - 1, H& - 1), Image& To(px(0), py(0))-(px(1), py(1))-(px(2), py(2))
    _MapTriangle (0, 0)-(W& - 1, 0)-(W& - 1, H& - 1), Image& To(px(0), py(0))-(px(3), py(3))-(px(2), py(2))
End Sub ' RotoZoom2

'' /////////////////////////////////////////////////////////////////////////////
'' https://qb64phoenix.com/forum/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%

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

$If  Then
    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
$End If

' /////////////////////////////////////////////////////////////////////////////
' 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

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

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

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

$If  Then
    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
$End If

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

$If  Then
    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
$End If

' /////////////////////////////////////////////////////////////////////////////
' 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 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

' same as DebugPrint but includes a timestamp
Sub DebugPrintTS (MyString As String)
    If m_bDebug = TRUE Then
        '_Echo MyString

        ReDim arrLines(-1) As String
        Dim iLoop As Integer
        Dim sTimeStamp As String
       
        sTimeStamp = GetCurrentDateTime$("{mm}/{dd}/{yyyy} {hh}:{nn}:{ss} {ampm}")
       
        split MyString, Chr$(13), arrLines()
        For iLoop = LBound(arrLines) To UBound(arrLines)
            _Echo sTimeStamp + " " + arrLines(iLoop)
        Next iLoop
    End If
End Sub ' DebugPrintTS

'' /////////////////////////////////////////////////////////////////////////////
'
'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%

' Screen _NewImage(1024, 768, 32): _ScreenMove 0, 0
' iCols = _Width(0) \ _FontWidth
' iRows = _Height(0) \ _FontHeight
' Print "_Width(0)  =" + _Trim$(Str$(_Width(0)))
' Print "_Height(0) =" + _Trim$(Str$(_Height(0)))
' Print "_FontWidth =" + _Trim$(Str$(_FontWidth))
' Print "_FontHeight=" + _Trim$(Str$(_FontHeight))
' Print "iCols = _Width(0)  \ _FontWidth  = " + _Trim$(Str$(iCols))
' Print "iRows = _Height(0) \ _FontHeight = " + _Trim$(Str$(iRows))

$If  Then
    'Pete:
    'Oh, does anyone else like how using $IF/THEN works as a block REM statement?
    'I mean I'd rather we had a QB64 block remark statement like the one used for JavaScript, but thi hack will do.
$End If

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

'#END

The vector polygon shape editor "VectorEditor59.bas": 

Code: (Select All)
Dim Shared m_sTitle As String: m_sTitle = "2D Vector Shapes v0.36 by madscijr"
_Title m_sTitle ' display in the Window's title bar

' Ship Editor / vector graphics editor for Fast Zap 'Em: Multispacewar

' DATE         WHO-DONE-IT   DID-WHAT
' 2022-11-21   madscijr      Version 0.01 borrowed graphics objects and format from Widescreen Asteroids by Terry Ritchie.
' 2022-12-12   madscijr      Version 0.58 (last version before project stalled!)
' 2025-01-05   madscijr      Version 0.59 (no changes, just refamiliarize with code and get project back on track)

' DONE
' * We now can draw opaque shapes (still need to get working for shapes with non-contiguous areas)
' * Display looks prettier!
'   - Shapes not active are drawn in an animated dashes
'   - Text display is color coded to shapes
' * Tried adding twinkling stars (hard to see, need to fix)
' * fix twinkling stars

' TODO:
' * get fill working for shapes with non-contiguous areas)
'   - define "fill coordinates" property or array
'   - auto find these in shape
'   - eliminate duplicates
'   - store the remaining values store in shape data
'   - use for PAINT when drawing fill color

' BOOLEAN CONSTANTS
Const FALSE = 0
Const TRUE = Not FALSE

' OTHER SETTINGS
Const cFPS = 120
Const cMinStars = 50
Const cMaxStars = 150
Const cSpeed = 2
Const cPolarMin = -63
Const cPolarMax = 63
Const cPolarNone = -64
Const cFlashCurrentLineMax = cFPS / 4 ' flash speed for currently selected line
Const cFlashStatusMessageMax = cFPS / 32 ' flash speed for status message

' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
' 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
    z As Integer ' zorder in which object is stacked (in front of or behind other objects)
    FillColor As _Unsigned Long ' color to fill in object with (use cEmpty for transparent)
    PreviewColor As _Unsigned Long
    
    ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
    xPos As Single 'Integer ' X co-ordinate of the ship
    yPos As Single '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 object heading
    facing As Single ' which direction is the object facing
    speed As Single ' how fast is the object going
    ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
    
End Type ' ObjectType

' HOLDS DEFINITION OF A LINE SEGMENT
Type LineType
    x1 As Integer
    y1 As Integer
    x2 As Integer
    y2 As Integer
    color As _Unsigned Long
    IsLast As Integer
    IsEnabled As Integer
End Type ' LineType

' HOLDS AN X,Y COORDINATE
Type CoordType
    x As Integer
    y As Integer
End Type ' CoordType

' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
' END SIMPLE VECTOR ENGINE TYPES
' ----------------------------------------------------------------------------------------------------------------------------------------------------------------

' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
' BEGIN CHEAPO PLANETARIUM TYPES
' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
' HOLDS LOCATION + COLOR OF EACH STAR
Type StarType
    x As Integer
    y As Integer
   
    ColorIndex As _Unsigned Long ' the star's current color
    TwinkleCounter As Integer ' counter for twinkles
    MaxTwinkCount As Integer ' controls how fast the star twinkles
   
    width As Integer
    MinWidth As Integer ' smallest width
    MaxWidth As Integer ' largest width
    WidthCounter As Integer ' counter for width
    MaxWidthCount As Integer ' controls how fast the star size fluctuates
    BigCounter As Integer ' counter for max width
    MaxBigCount As Integer ' controls how long the star stays big
End Type ' StarType
' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
' END CHEAPO PLANETARIUM 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$ = "0.57"

' OTHER USEFUL VARIABLES
Dim Shared PI As Single: PI = 4 * Atn(1)
Dim Shared iFPS As Integer: iFPS = 60 ' Delay between frames (frames per second)
' OBJECT VARIABLES
ReDim Shared m_arrObject(1 To 32) As ObjectType ' m_arrObject(ObjectIndex)
ReDim Shared m_arrOrder(1 To 32) As Integer
ReDim Shared m_arrLines(1 To 32, 1 To 32) As LineType ' m_arrLines(ObjectIndex, LineIndex)
ReDim Shared m_arrLineStyle(1 To 8) As Long
ReDim Shared m_arrStars(1 To cMaxStars) As StarType
ReDim Shared m_arrColor(-1) As _Unsigned Long
ReDim Shared m_arrGrayColor(-1) As _Unsigned Long

' ****************************************************************************************************************************************************************
' 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
'DrawVectorObjectTest1
EditVectorObject1

' =============================================================================
' 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 BOX DRAWING ROUTINES
' ################################################################################################################################################################

' /////////////////////////////////////////////////////////////////////////////
' DRAW A 2-D BOX (OUTLINE)
' https://www.qb64.org/wiki/LINE

Sub DrawBoxOutline (iX As Integer, iY As Integer, iSize As Integer, fgColor As _Unsigned Long)
    Line (iX, iY)-(iX + (iSize - 1), iY + (iSize - 1)), fgColor, B ' Draw box outline
End Sub ' DrawBoxOutline

' /////////////////////////////////////////////////////////////////////////////
' DRAW A 2-D BOX (SOLID)
' https://www.qb64.org/wiki/LINE

' Renamed DrawBox/DrawBoxLine to DrawSolidBox

Sub DrawBoxSolid (iX As Integer, iY As Integer, iSize As Integer, fgColor As _Unsigned Long)
    Line (iX, iY)-(iX + (iSize - 1), iY + (iSize - 1)), fgColor, BF ' Draw a solid box
End Sub ' DrawBoxSolid

' /////////////////////////////////////////////////////////////////////////////
' DRAW A 2-D RECTANGLE

'DrawRect 0, iX, iY, iSizeW, iSizeH, fgColor, bgColor

Sub DrawRect (img&, iX As Integer, iY As Integer, iSizeW As Integer, iSizeH As Integer, fgColor As _Unsigned Long, bgColor As _Unsigned Long)
    'If img& < -1 Then
    If img& <= 0 Then
        ' Select target image
        _Dest img& ': Cls , cEmpty
       
        ' Draw fill (bgColor)
        If bgColor <> cEmpty Then
            Line (iX, iY)-(iX + (iSizeW - 1), iY + (iSizeH - 1)), bgColor, BF ' Draw a solid rectangle
        End If
       
        ' Draw outline (fgColor)
        If fgColor <> cEmpty Then
            Line (iX, iY)-(iX + (iSizeW - 1), iY + (iSizeH - 1)), fgColor, B ' Draw rectangle outline
        End If
    End If
End Sub ' DrawRect

' /////////////////////////////////////////////////////////////////////////////
' 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

'DrawCircle 0, iX, iY, iRadius, iThickness, fgColor, bgColor
Sub DrawCircle (img&, iX As Integer, iY As Integer, iRadius As Integer, iThickness As Integer, fgColor As _Unsigned Long, bgColor As _Unsigned Long)
    Dim iLoop As Integer
    Dim iNextRadius As Integer
    Dim iRadiusError As Integer
    Dim iNextX As Integer
    Dim iNextY As Integer
    If (img& <= 0) And (iThickness > 0) And (fgColor <> cEmpty) Then
        ' Select target image
        _Dest img& ': Cls , cEmpty
       
        ' Draw circle fill
        If bgColor <> cEmpty Then
            iNextRadius = Abs(iRadius)
            iRadiusError = -iNextRadius
            iNextX = iNextRadius
            iNextY = 0
            If iNextRadius = 0 Then
                PSet (iX, iY), bgColor
            Else
                ' 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 (iX - iNextX, iY)-(iX + iNextX, iY), bgColor, BF
                While iNextX > iNextY
                    iRadiusError = iRadiusError + iNextY * 2 + 1
                    If iRadiusError >= 0 Then
                        If iNextX <> iNextY + 1 Then
                            Line (iX - iNextY, iY - iNextX)-(iX + iNextY, iY - iNextX), bgColor, BF
                            Line (iX - iNextY, iY + iNextX)-(iX + iNextY, iY + iNextX), bgColor, BF
                        End If
                        iNextX = iNextX - 1
                        iRadiusError = iRadiusError - iNextX * 2
                    End If
                    iNextY = iNextY + 1
                    Line (iX - iNextX, iY - iNextY)-(iX + iNextX, iY - iNextY), bgColor, BF
                    Line (iX - iNextX, iY + iNextY)-(iX + iNextX, iY + iNextY), bgColor, BF
                Wend
            End If
        End If
       
        ' Draw circle outline
        If fgColor <> cEmpty Then
            If iRadius = 0 Then
                PSet (iX, iY), fgColor
            Else
                iNextRadius = iRadius
                For iLoop = 1 To iThickness
                   
                    ' DRAW CIRCLE
                    ' CIRCLE (x, y), radius, color
                    'CIRCLE [[[STEP]]](column, row), radius%, [drawColor%][, startRadian!, stopRadian!] [, aspect!]
                    Circle (iX, iY), iNextRadius, fgColor
                   
                    'Line (iLoop * 8, iHeight * 16)-(iLoop * 8 + 8, _Height), cGray, BF
                    'Circle (iX, iY), 4, cRed
                   
                    iNextRadius = iNextRadius - 1
                    If iNextRadius = 0 Then
                        PSet (iX, iY), fgColor
                        Exit For
                    End If
                Next iLoop
            End If
        End If
       
    End If
End Sub ' DrawCircle

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

'DrawCircleOutline 0, iX, iY, iRadius, iThickness, fgColor
Sub DrawCircleOutline (img&, iX As Integer, iY As Integer, iRadius As Integer, iThickness As Integer, fgColor As _Unsigned Long)
    Dim iNextRadius As Integer
    Dim iLoop As Integer
    If (img& <= 0) And (iThickness > 0) And (fgColor <> cEmpty) Then
        ' Select target image
        _Dest img& ': Cls , cEmpty
       
        ' Initialize
        iNextRadius = iRadius
       
        ' Draw circle
        If Radius = 0 Then
            PSet (iX, iY), fgColor
        Else
            For iLoop = 1 To iThickness
               
                ' DRAW CIRCLE
                ' CIRCLE (x, y), radius, color
                'CIRCLE [[[STEP]]](column, row), radius%, [drawColor%][, startRadian!, stopRadian!] [, aspect!]
                Circle (iX, iY), iNextRadius, fgColor
               
                'Line (iLoop * 8, iHeight * 16)-(iLoop * 8 + 8, _Height), cGray, BF
                'Circle (iX, iY), 4, cRed
               
                iNextRadius = iNextRadius - 1
                If iNextRadius = 0 Then
                    PSet (iX, iY), fgColor
                    Exit For
                End If
            Next iLoop
        End If
    End If
End Sub ' DrawCircleOutline

' /////////////////////////////////////////////////////////////////////////////
' DRAW A 2-D RECTANGLE (OUTLINE)

'DrawRectOutline iX, iY, iSizeW, iSizeH, fgColor

Sub DrawRectOutline (iX As Integer, iY As Integer, iSizeW As Integer, iSizeH As Integer, fgColor As _Unsigned Long)
    Line (iX, iY)-(iX + (iSizeW - 1), iY + (iSizeH - 1)), fgColor, B ' Draw rectangle outline
End Sub ' DrawRectOutline

' /////////////////////////////////////////////////////////////////////////////
' DRAW A 2-D RECTANGLE (SOLID)

'DrawRectSolid iX, iY, iSizeW, iSizeH, fgColor

Sub DrawRectSolid (iX As Integer, iY As Integer, iSizeW As Integer, iSizeH As Integer, fgColor As _Unsigned Long)
    Line (iX, iY)-(iX + (iSizeW - 1), iY + (iSizeH - 1)), fgColor, BF ' Draw a solid rectangle
End Sub ' DrawRectSolid

' /////////////////////////////////////////////////////////////////////////////
' DRAW A 2-D BOX (OUTLINE)
' https://www.qb64.org/wiki/LINE

' The style parameter 0-255 doesn't seem to have a solid line?
' For that, use DrawOutlineBox.

' LINE [STEP] [(column1, row1)]-[STEP] (column2, row2), color[, [{B|BF}], style%]
' B creates a box outline with each side parallel to the program screen sides. BF creates a filled box.
' The style% signed INTEGER value sets a dotted pattern to draw the line or rectangle outline.

Sub DrawStyledOutlineBox (iX%, iY%, iSize%, iColor~&, iStyle%)
    Line (iX%, iY%)-(iX% + (iSize% - 1), iY% + (iSize% - 1)), iColor~&, B , iStyle%
End Sub ' DrawStyledOutlineBox

' /////////////////////////////////////////////////////////////////////////////
' DRAW A 2-D BOX (OUTLINE) WITH A SOLID LINE

Sub DrawOutlineBox (iX%, iY%, iSize2%, iColor~&, iWeight2%)
    Dim iFromX%
    Dim iFromY%
    Dim iToX%
    Dim iToY%
    iSize% = iSize2% - 1
    iWeight% = iWeight2% - 1
    If iWeight% = 0 Then
        ' TOP LINE
        iFromX% = iX%
        iFromY% = iY%
        iToX% = iX% + iSize%
        iToY% = iY%
        Line (iFromX%, iFromY%)-(iToX%, iToY%), iColor~&, BF

        ' BOTTOM LINE
        iFromX% = iX%
        iFromY% = iY% + iSize%
        iToX% = iX% + iSize%
        iToY% = iY% + iSize%
        Line (iFromX%, iFromY%)-(iToX%, iToY%), iColor~&, BF

        ' LEFT LINE
        iFromX% = iX%
        iFromY% = iY%
        iToX% = iX%
        iToY% = iY% + iSize%
        Line (iFromX%, iFromY%)-(iToX%, iToY%), iColor~&, BF

        ' RIGHT LINE
        iFromX% = iX% + iSize%
        iFromY% = iY%
        iToX% = iX% + iSize%
        iToY% = iY% + iSize%
        Line (iFromX%, iFromY%)-(iToX%, iToY%), iColor~&, BF
    ElseIf iWeight% > 0 Then
        ' TOP LINE
        For iFromY% = iY% To (iY% + iWeight%)
            iFromX% = iX%
            'iFromY% = iY%
            iToX% = iX% + iSize%
            iToY% = iFromY%
            Line (iFromX%, iFromY%)-(iToX%, iToY%), iColor~&, BF
        Next iFromY%

        ' BOTTOM LINE
        For iFromY% = ((iY% + iSize%) - iWeight%) To (iY% + iSize%)
            iFromX% = iX%
            'iFromY% = iY% + iSize%
            iToX% = iX% + iSize%
            iToY% = iFromY%
            Line (iFromX%, iFromY%)-(iToX%, iToY%), iColor~&, BF
        Next iFromY%

        ' LEFT LINE
        For iFromX% = iX% To (iX% + iWeight%)
            'iFromX% = iX%
            iFromY% = iY%
            iToX% = iFromX%
            iToY% = iY% + iSize%
            Line (iFromX%, iFromY%)-(iToX%, iToY%), iColor~&, BF
        Next iFromX%

        ' RIGHT LINE
        For iFromX% = ((iX% + iSize%) - iWeight%) To (iX% + iSize%)
            'iFromX% = iX% + iSize%
            iFromY% = iY%
            iToX% = iFromX%
            iToY% = iY% + iSize%
            Line (iFromX%, iFromY%)-(iToX%, iToY%), iColor~&, BF
        Next iFromX%
    End If
End Sub ' DrawOutlineBox

' /////////////////////////////////////////////////////////////////////////////
'DrawSquare 0, x1, y1, size, fgcolor, bgcolor

Sub DrawSquare (img&, x1%, y1%, size%, fgcolor~&, bgcolor~&)
    Dim x2%, y2%
    If img& < -1 Then
        _Dest img& ': Cls , cEmpty

        x2% = (x1% + size%) - 1
        y2% = (y1% + size%) - 1

        Line (x1%, y1%)-(x2%, y1%), fgcolor~&, , 65535
        Line (x2%, y1%)-(x2%, y2%), fgcolor~&, , 65535
        Line (x2%, y2%)-(x1%, y2%), fgcolor~&, , 65535
        Line (x1%, y2%)-(x1%, y1%), fgcolor~&, , 65535

        If bgcolor~& <> cEmpty Then
            'PAINT [STEP] (column%, row%), fillColor[, borderColor%]
            Paint (x1% + 1, y1% + 1), bgcolor~&, fgcolor~&
        End If
    End If
End Sub ' Draw Square

' ################################################################################################################################################################
' END BOX DRAWING ROUTINES
' ################################################################################################################################################################

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

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

Sub EditVectorObject1
    Dim RoutineName As String: RoutineName = "EditVectorObject1"
    Dim iMouseX As Integer
    Dim iMouseY As Integer
    Dim iX1 As Integer: iX1 = 0
    Dim iY1 As Integer: iY1 = 0
    Dim iX2 As Integer: iX2 = 0
    Dim iY2 As Integer: iY2 = 0
    Dim iPX1 As Integer ' polar x1 coordinate
    Dim iPY1 As Integer ' polar y1 coordinate
    Dim iPX2 As Integer ' polar x2 coordinate
    Dim iPY2 As Integer ' polar y2 coordinate
    Dim iCursorX As Integer
    Dim iCursorY As Integer
    Dim iCX As Integer ' center x = screen coordinate for polar 0,0
    Dim iCY As Integer ' center y = screen coordinate for polar 0,0
    Dim bLeftClick As Integer: bLeftClick = FALSE
    Dim bRightClick As Integer: bRightClick = FALSE
    Dim bOldLeftClick As Integer: bOldLeftClick = FALSE
    Dim bOldRightClick As Integer: bOldRightClick = FALSE
    Dim iCellSize As Integer
    Dim iGridSize As Integer
    Dim iRadius As Integer
    Dim iNextX As Integer
    Dim iNextY As Integer
    Dim LineColor As _Unsigned Long
    Dim FontFgColor As _Unsigned Long
    Dim FontBgColor As _Unsigned Long
    Dim NewColor As _Unsigned Long
    Dim iColorIndex As Integer
    Dim iIndex As Integer
   
    Dim iStartCol As Integer
    Dim iNextCol As Integer
    Dim iColWidth As Integer
    Dim iColHeight As Integer
    Dim iStartRow As Integer
    Dim iNextRow As Integer
    Dim iRowHeight As Integer
    Dim iRowCount As Integer
    Dim iBottomRow As Integer
    Dim sFlag As String
    Dim bEnableSelect As Integer: bEnableSelect = FALSE
    Dim iSelectX As Integer ' when bEnableSelect=TRUE this contains the text click psition in the right (select) pane
    Dim iSelectY As Integer ' when bEnableSelect=TRUE this contains the text click psition in the right (select) pane
    Dim iPolarX As Integer ' when bEnableSelect=FALSE this contains the polar coordinate click position of the current object
    Dim iPolarY As Integer ' when bEnableSelect=FALSE this contains the polar coordinate click position of the current object
   
    Dim iConnectionCount1 As Integer
    Dim iConnectionCount2 As Integer
    Dim iPreviewX As Integer
    Dim iPreviewY As Integer
    Dim iCount As Integer
   
    Dim iNextObject As Integer ' temp variable for looping through objects
    Dim iSelectedObject As Integer ' the currently selected object
   
    Dim iNextLine As Integer ' temp variable for looping through lines
    Dim iSelectedLine As Integer ' the currently selected line
    Dim iFirstLine As Integer ' line we started searching at
    Dim iFoundLine As Integer
   
    Dim SelectColor As _Unsigned Long
    Dim iSelectCount As Integer
   
    Dim iClickedPolarX As Integer
    Dim iClickedPolarY As Integer
    Dim bFirstClick As Integer
    
    Dim iEditX As Integer ' x of selected point
    Dim iEditY As Integer ' y of selected point
    Dim iEditWhich As Integer ' 1 means editing x1,y1 else 2 means editing x2,y2
    Dim iLastKey As Integer
    Dim sCommand As String
   
    Dim iNewPolarX As Integer ' x of new point
    Dim iNewPolarY As Integer ' y of new point
    Dim iLineCount As Integer ' used to count lines
   
    Dim sStatus As String ' status message to display to user
    Dim StatusColor As _Unsigned Long
    Dim iStatusCount As Integer
    Dim iStatusColorIndex As Integer
   
    Dim oPreview As ObjectType
   
    Dim iMinX As Integer
    Dim iMaxX As Integer
    Dim iMinY As Integer
    Dim iMaxY As Integer
    Dim imgObjects& ' used for drawing objects
    Dim imgTemp& ' temporary drawing area
   
    Dim lngSolidLineStyle ' selected object's line style
   
    ' INITIALIZE
    InitializeRandom
    iMinX = 0
    iMaxX = 1024
    iMinY = 0
    iMaxY = 768
    iSelectedObject = 1
    iSelectedLine = LBound(m_arrLines, 2) - 1 ' none selected
    SelectColor = cEmpty
    iSelectCount = 0
    iClickedPolarX = cPolarNone
    iClickedPolarY = cPolarNone
    iNewPolarX = cPolarNone
    iNewPolarY = cPolarNone
    iLastKey = 0
    sCommand = ""
    AddGrayscaleColors m_arrGrayColor() ' SAVE COLORS FOR STARS AND GRAYSCALE OBJECTS
    sStatus = "hello world"
    StatusColor = cEmpty
    iStatusCount = 0
    iStatusColorIndex = 0
    lngSolidLineStyle = 65535
    bFirstClick = TRUE
    
    ' USE LATER FOR DRAWING LAYERS:
    imgObjects& = _NewImage(iMaxX, iMaxY, 32) ' frontground objects
    imgTemp& = _NewImage(iMaxX, iMaxY, 32) ' temporary
   
    ' INIT SCREEN
    Screen _NewImage(iMaxX, iMaxY, 32): _ScreenMove 0, 0
    _Dest 0: Cls , cBlack
   
    ' INIT PREVIEW OBJECT
    oPreview.x = _Width - (23 * _FontWidth) ' (96 + 88)
    oPreview.y = _FontHeight * 10 ' 96 + 32 + (_FontWidth * 2)
    oPreview.dx = RandomNumber%(-5, 5)
    oPreview.dy = RandomNumber%(-5, 5)
    oPreview.cx = 0
    oPreview.cy = 0
    oPreview.z = 0 ' 0 = front
    oPreview.FillColor = cBlack
    oPreview.IsEnabled = TRUE
    oPreview.heading = 0
    oPreview.facing = 0
    oPreview.speed = 0
    
    ' SAVE COLORS FOR SHAPES
    AddShapeColors m_arrColor()
   
    ' GET OBJECT PREVIEW POSITION
    iPreviewX = _Width - (23 * _FontWidth) ' (96 + 88)
    iPreviewY = _FontHeight * 10 ' 96 + 32 + (_FontWidth * 2)
   
    ' GET CURSOR SIZE
    If _Height < _Width Then
        iCellSize = _Height \ 128
    Else
        iCellSize = _Width \ 128
    End If
    iRadius = iCellSize \ 2
    iGridSize = iCellSize * 126
   
    ' GET TEXT POSITION
    iStartCol = (iGridSize + (iCellSize * 3)) \ _FontWidth '_Width
    iColWidth = 15
    iColHeight = ((UBound(m_arrObject) - LBound(m_arrObject)) + 1) \ 2
    iRowHeight = _FontHeight * 2
    iStartRow = 15 ' leave enough room for 32 objects
    iBottomRow = ((iStartRow + iColHeight) - 1) * 2
   
    ' LOAD EXISTING OBJECTS
    InitVectorObjects
   
    ' SAVE COLORS FOR EACH OBJECT (FROM FIRST LINE SEGMENT) TO ARRAY FOR TEXT
    ' AND SET COORDINATES TO AREA OF SCREEN FOR PREVIEW
    iNextRow = iStartRow
    iNextCol = iStartCol
    iRowCount = 0
    For iNextObject = LBound(m_arrObject) To UBound(m_arrObject)
        If m_arrObject(iNextObject).IsEnabled = TRUE Then
            iNextLine = LBound(m_arrLines, 2)
            If m_arrLines(iNextObject, iNextLine).IsEnabled = TRUE Then
                m_arrObject(iNextObject).PreviewColor = m_arrLines(iNextObject, iNextLine).color
            Else
                m_arrObject(iNextObject).PreviewColor = cGray
            End If
        Else
            m_arrObject(iNextObject).PreviewColor = cGray
        End If
       
        ' GET OBJECT THUMBNAIL POSITION
        iRowCount = iRowCount + 1
        If iRowCount <= iColHeight Then
            iNextRow = iNextRow + 2
        Else
            iRowCount = 0
            iNextRow = iStartRow + 2
            iNextCol = iNextCol + iColWidth
        End If
        m_arrObject(iNextObject).x = (iNextCol + iColWidth) * _FontWidth
        m_arrObject(iNextObject).y = (iNextRow * _FontHeight) - _FontHeight
    Next iNextObject
   
    ' INITIALIZE CURRENT COLOR
    NewColor = m_arrObject(iSelectedObject).PreviewColor
    iColorIndex = LBound(m_arrColor)
    For iIndex = LBound(m_arrColor) To UBound(m_arrColor)
        If m_arrColor(iIndex) = NewColor Then
            iColorIndex = iIndex
            Exit For
        End If
    Next iIndex
   
    ' CALCULATE SCREEN POSITION FOR POLAR (0,0)
    iCX = (63 * iCellSize) '+ iRadius
    iCY = (63 * iCellSize) '+ iRadius
   
    ' GET INPUT
    _MouseHide ' hide OS mouse pointer
    Do While _MouseInput: Loop
    Do
        ' RESET DISPLAY
        _Dest 0: Cls , cBlack ' CLEAR SCREEN
        _Dest imgObjects&: Cls , cEmpty ' CLEAR OBJECTS LAYER
        _Dest 0
       
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        ' BEGIN DRAW GRID
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        LineColor = _RGB32(105, 105, 105, 64)
       
        ' VERTICAL LINES:
        For iNextX = 0 To (iGridSize + iCellSize) Step iCellSize
            Line (iNextX, 1)-(iNextX, iGridSize + iCellSize), LineColor, BF
        Next iNextX
       
        ' HORIZONTAL LINES:
        For iNextY = 0 To (iGridSize + iCellSize) Step iCellSize
            For iNextX = 0 To iGridSize Step iCellSize
                Line ((iNextX + 1), iNextY)-((iNextX + iCellSize) - 1, iNextY), LineColor, BF
            Next iNextX
        Next iNextY
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        ' END DRAW GRID
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
       
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        ' BEGIN GET CURSOR POSITION & QUANTIZE
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        ' WHAT THIS SECTION DOES:
        ' Get raw mouse coordinates, and arrive at:
        ' * iX1 = 0-126
        ' * iY1 = 0-126
        ' * iCursorX = click position quantized to iCellSize
        ' * iCursorY = click position quantized to iCellSize
        ' * iPolarX = polar coordinates -63 to 63
        ' * iPolarY = polar coordinates -63 to 63
       
        ' GET RAW MOUSE COORDINATES
        iMouseX = _MouseX
        iMouseY = _MouseY
       
        ' DIVIDE BY CELL SIZE
        iX1 = (iMouseX \ iCellSize) ' _FontWidth
        iY1 = (iMouseY \ iCellSize) ' _FontHeight
       
        ' FIX X POSITION
        If iX1 < 0 Then
            iX1 = 0
            bEnableSelect = FALSE
        ElseIf iX1 > 126 Then
            iX1 = 126
           
            ' IF USER IS EDITING A LINE,
            ' DON'T LET THEM EXIT UNTIL DONE (OR THEY PRESS ESC)
            ' ENABLE CURSOR FOR RIGHT (TEXT) SIDE OF SCREEN
            bEnableSelect = TRUE
            iSelectX = (iMouseX \ _FontWidth) '* _FontWidth
            iSelectY = (iMouseY \ _FontHeight) '* _FontHeight
        Else
            bEnableSelect = FALSE
        End If
       
        ' FIX Y POSITION
        If iY1 < 0 Then
            iY1 = 0
        ElseIf iY1 > 126 Then
            iY1 = 126
        End If
       
        ' QUANTIZE TO CELL SIZE
        iCursorX = iX1 * iCellSize ' click position quantized to iCellSize
        iCursorY = iY1 * iCellSize ' click position quantized to iCellSize
       
        ' GET POLAR COORDINATE
        iPolarX = iX1 - 63
        iPolarY = iY1 - 63
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        ' END GET CURSOR POSITION & QUANTIZE
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
       
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        ' FLASH STATUS MESSAGE
        iStatusCount = iStatusCount + 1
        If iStatusCount > cFlashStatusMessageMax Then
            iStatusCount = 0
            iStatusColorIndex = iStatusColorIndex + 1
            If iStatusColorIndex > UBound(m_arrGrayColor) Then
                iStatusColorIndex = 0
            End If
            StatusColor = m_arrGrayColor(iStatusColorIndex)
        End If
       
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        ' BEGIN SHOW TEXT
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        FontBgColor = _RGB32(0, 0, 0, 128)
       
        ' SHOW LABELS
        Color cWhite, FontBgColor
        PrintString 0, iStartCol + 0, "iPolarX        ="
        PrintString 1, iStartCol + 0, "iPolarY        ="
        PrintString 2, iStartCol + 0, "Color          ="
       
        ' SHOW VALUES
        Color cWhite, FontBgColor
        PrintString 0, iStartCol + 16, Left$(cstr$(iPolarX) + "     ", 5)
        PrintString 1, iStartCol + 16, Left$(cstr$(iPolarY) + "     ", 5)
       
        ' SHOW CURRENT COLOR
        Color m_arrColor(iColorIndex), FontBgColor
        PrintString 2, iStartCol + 16, Left$(ColorName$(m_arrColor(iColorIndex)) + "                ", 16)
       
        ' SHOW STATUS
        Color StatusColor, FontBgColor
        PrintString 11, iStartCol + 0, Left$(sStatus + String$(31, " "), 31)
       
        ' SHOW INSTRUCTIONS
        Color cWhite, FontBgColor
        PrintString 13, iStartCol + 0, "PRESS <ESC> TO EXIT  "
       
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        ' END SHOW TEXT
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
       
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        ' BEGIN SHOW OBJECT NAMES AND THUMBNAILS
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        'TODO: instead of "->" draw a flashing box around the current object
        iNextRow = iStartRow
        iNextCol = iStartCol
        iRowCount = 0
        For iNextObject = LBound(m_arrObject) To UBound(m_arrObject)
            If m_arrObject(iNextObject).IsEnabled = TRUE Then
                FontFgColor = m_arrObject(iNextObject).PreviewColor
                If iNextObject = iSelectedObject Then
                    sFlag = "-> "
                Else
                    sFlag = "   "
                End If
            Else
                FontFgColor = cGray
                sFlag = "   "
            End If
            Color FontFgColor, FontBgColor ' cBlack ' cEmpty 'cBlack
            PrintAt iNextRow, iNextCol, sFlag + "Object #" + cstr$(iNextObject)
           
            ' ADVANCE TO NEXT
            iRowCount = iRowCount + 1
            If iRowCount < iColHeight Then
                iNextRow = iNextRow + 2
            Else
                iRowCount = 0
                iNextRow = iStartRow
                iNextCol = iNextCol + iColWidth
            End If
           
            ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
            ' BEGIN DRAW OBJECT'S LINE SEGMENTS (THUMBNAIL)
            ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
            For iNextLine = LBound(m_arrLines, 2) To UBound(m_arrLines, 2)
                Line _
                    (m_arrObject(iNextObject).x + (m_arrLines(iNextObject, iNextLine).x1 \ 4), _
                    m_arrObject(iNextObject).y + (m_arrLines(iNextObject, iNextLine).y1 \ 4)) _
                    - _
                    (m_arrObject(iNextObject).x + (m_arrLines(iNextObject, iNextLine).x2 \ 4), _
                    m_arrObject(iNextObject).y + (m_arrLines(iNextObject, iNextLine).y2 \ 4) ) _
                    , _
                    m_arrLines(iNextObject, iNextLine).color ' , BF
                If m_arrLines(iNextObject, iNextLine).IsLast = TRUE Then
                    Exit For
                End If
            Next iNextLine
            ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
            ' END DRAW OBJECT'S LINE SEGMENTS (THUMBNAIL)
            ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        Next iNextObject
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        ' END SHOW OBJECT NAMES AND THUMBNAILS
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
       
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        ' SHOW SELECT CURSOR
        If bEnableSelect = TRUE Then
            Color cBlack, cWhite
            _PrintString (iSelectX * _FontWidth, iSelectY * _FontHeight), " "
        End If
       
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        ' FLASH SELECTED LINE
        iSelectCount = iSelectCount + 1
        If iSelectCount > cFlashCurrentLineMax Then
            iSelectCount = 0
            If SelectColor = cBlack Then
                If iSelectedLine >= LBound(m_arrLines, 2) Then
                    If iSelectedLine <= UBound(m_arrLines, 2) Then
                        SelectColor = m_arrLines(iSelectedObject, iSelectedLine).color
                    End If
                End If
            Else
                SelectColor = cBlack
            End If
        End If
       
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        ' BEGIN DRAW OBJECT'S LINE SEGMENTS (EDITOR)
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        For iNextLine = LBound(m_arrLines, 2) To UBound(m_arrLines, 2)
            If m_arrLines(iSelectedObject, iNextLine).IsEnabled = TRUE Then
                ' CALCULATE POSITIONS ON THE EDIT GRID
                iX1 = ((m_arrLines(iSelectedObject, iNextLine).x1 + 63) * iCellSize) + iRadius
                iY1 = ((m_arrLines(iSelectedObject, iNextLine).y1 + 63) * iCellSize) + iRadius
                iX2 = ((m_arrLines(iSelectedObject, iNextLine).x2 + 63) * iCellSize) + iRadius
                iY2 = ((m_arrLines(iSelectedObject, iNextLine).y2 + 63) * iCellSize) + iRadius
               
                ' FLASH COLOR FOR SELECTED LINE
                If iNextLine = iSelectedLine Then
                    LineColor = SelectColor
                Else
                    LineColor = m_arrLines(iSelectedObject, iNextLine).color
                End If
               
                ' DRAW THE LINE
                Line (iX1, iY1)-(iX2, iY2), LineColor ' , BF
               
                ' SHOW ENDPOINTS
                DrawCircle 0, iX1, iY1, iRadius, 1, LineColor, LineColor
                DrawCircle 0, iX2, iY2, iRadius, 1, LineColor, LineColor
            Else
                ' (SHOULD WE SHOW DISABLED POINTS?)
            End If
           
            If m_arrLines(iSelectedObject, iNextLine).IsLast = TRUE Then
                Exit For
            End If
        Next iNextLine
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        ' END DRAW OBJECT'S LINE SEGMENTS (EDITOR)
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
       
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        ' BEGIN MOVE PREVIEW OBJECT
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        ' Move along X axis
        oPreview.cx = oPreview.cx + 1
        If oPreview.cx > (10 - Abs(oPreview.dx)) Then
            oPreview.cx = 0
            If oPreview.dx < 0 Then
                oPreview.x = oPreview.x - cSpeed
                If oPreview.x < iMinX Then
                    oPreview.x = iMaxX
                End If
            ElseIf oPreview.dx > 0 Then
                oPreview.x = oPreview.x + cSpeed
                If oPreview.x > iMaxX Then
                    oPreview.x = iMinX
                End If
            End If
        End If
       
        ' Move along Y axis
        oPreview.cy = oPreview.cy + 1
        If oPreview.cy > (10 - Abs(oPreview.dy)) Then
            oPreview.cy = 0
            If oPreview.dy < 0 Then
                oPreview.y = oPreview.y - cSpeed
                If oPreview.y < iMinY Then
                    oPreview.y = iMaxY
                End If
            ElseIf oPreview.dy > 0 Then
                oPreview.y = oPreview.y + cSpeed
                If oPreview.y > iMaxY Then
                    oPreview.y = iMinY
                End If
            End If
        End If
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        ' END MOVE PREVIEW OBJECT
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
       
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        ' BEGIN DRAW OBJECT'S LINE SEGMENTS (PREVIEW)
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        ' Clear temporary (layer and draw on it
        _Dest imgTemp&: Cls , cEmpty
       
        ' Draw object's line segments
        For iNextLine = LBound(m_arrLines, 2) To UBound(m_arrLines, 2)
            If m_arrLines(iSelectedObject, iNextLine).IsLast = FALSE Then
                If m_arrLines(iSelectedObject, iNextLine).IsEnabled = TRUE Then
                    'Line (x1%, y1%)-(x2%, y2%), arrColor(arrStars(iLoop).ColorIndex), BF, lngLineStyle
                    Line _
                        (oPreview.x + m_arrLines(iSelectedObject, iNextLine).x1, _
                        oPreview.y + m_arrLines(iSelectedObject, iNextLine).y1) _
                        - _
                        (oPreview.x + m_arrLines(iSelectedObject, iNextLine).x2, _
                        oPreview.y + m_arrLines(iSelectedObject, iNextLine).y2) _
                        , _
                        m_arrLines(iSelectedObject, iNextLine).color, , lngSolidLineStyle
                End If
            Else
                Exit For
            End If
        Next iNextLine
       
        'TODO:
        '' Draw fill color if not transparent
        'If oPreview.FillColor <> cEmpty Then
        '    ' Fill in current object with its fill color...
        '    'PAINT [STEP] (column%, row%), fillColor[, borderColor%]
        '    Paint (oPreview.x, oPreview.y), oPreview.FillColor, oPreview.PreviewColor
        'End If
       
        ' Add new object to objects layer
        '_PUTIMAGE , sourceHandle&, destHandle& 'size full source to fit full destination area
        _PutImage , imgTemp&, imgObjects&
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        ' END DRAW OBJECT'S LINE SEGMENTS (PREVIEW)
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
       
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        ' DRAW VECTOR CURSOR
        DrawRect 0, iCursorX, iCursorY, iCellSize, iCellSize, cCyan, cEmpty
       
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        ' BEGIN LEFT CLICK
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        bLeftClick = _MouseButton(1)
        If bLeftClick Then
            If bOldLeftClick = FALSE Then
                If bEnableSelect = TRUE Then
                    ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
                    ' BEGIN CLICK RIGHT PANE / SELECT OBJECT
                    ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
                    If iSelectY >= iStartRow And iSelectY < iBottomRow Then
                        ' every other line:
                        iSelectedObject = iSelectY - (iStartRow - 1)
                        iSelectedObject = ((iSelectedObject + 1) \ 2)

                        ' clicked on column 2:
                        If iSelectX >= (iStartCol + iColWidth) Then
                            iSelectedObject = iSelectedObject + iColHeight
                        End If
                       
                        ' out of bounds?
                        If iSelectedObject > UBound(m_arrLines, 1) Then
                            iSelectedObject = UBound(m_arrLines, 1)
                        End If
                       
                        ' INITIALIZE CURRENT COLOR
                        NewColor = m_arrObject(iSelectedObject).PreviewColor
                        iColorIndex = LBound(m_arrColor)
                        For iIndex = LBound(m_arrColor) To UBound(m_arrColor)
                            If m_arrColor(iIndex) = NewColor Then
                                iColorIndex = iIndex
                                Exit For
                            End If
                        Next iIndex
                       
                    End If
                    ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
                    ' END CLICK RIGHT PANE / SELECT OBJECT
                    ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
                Else
' ****************************************************************************************************************************************************************
' LEFT CLICK LEFT PANE
' ****************************************************************************************************************************************************************
                    ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
                    ' BEGIN CLICK LEFT PANE / ANCHOR POINTS
                    ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
                   
                    ' DID USER CLICK ON A NEW POINT?
                    If iPolarX <> iClickedPolarX Or iPolarY <> iClickedPolarY Then
                        ' FIRST CLICK?
                        if bFirstClick = TRUE then
                            ' FIRST CLICK, REMEMBER THIS POINT
                            iClickedPolarX = iPolarX
                            iClickedPolarY = iPolarY
                            bFirstClick = FALSE
                            sStatus = "insert point"
                        else
                            ' INSERT NEW LINE FROM PREVIOUS POINT TO HERE
                            
                            ' HAVE WE USED THE MAX # OF LINES FOR THIS SHAPE?
                            iFoundLine = LBound(m_arrLines, 2) - 1
                            For iNextLine = LBound(m_arrLines, 2) To UBound(m_arrLines, 2)
                                If m_arrLines(iSelectedObject, iNextLine).IsEnabled = FALSE Then
                                    iFoundLine = iNextLine
                                    Exit For
                                End If
                            Next iNextLine                            
                            
                            ' INSERT NEW LINE
                            if iFoundLine >= LBound(m_arrLines, 2) then
                                m_arrLines(iSelectedObject, iFoundLine).x1 = iClickedPolarX
                                m_arrLines(iSelectedObject, iFoundLine).y1 = iClickedPolarY
                                m_arrLines(iSelectedObject, iFoundLine).x2 = iPolarX
                                m_arrLines(iSelectedObject, iFoundLine).y2 = iPolarY
                                m_arrLines(iSelectedObject, iFoundLine).color = m_arrColor(iColorIndex)
                                m_arrLines(iSelectedObject, iFoundLine).IsEnabled = TRUE
                                
                                sStatus = "inserted at " + _Trim$(Str$(iFoundLine))
                            else
                                sStatus = "Can't insert: max lines used!"
                            end if
                            
                            ' RESET FLAG
                            bFirstClick = TRUE
                        end if
                        
                        
                        
                        iSelectedLine = LBound(m_arrLines, 2) - 1 ' none selected
                    Else
                        ' USER CLICKED ON SAME POINT, ARE WE IN INSERT MODE?
                        if iClickCount = 1 then
                            ' INSERT SINGLE POINT HERE
                            
                        end if
                    End If
                   
                    ' GOTO NEXT POINT
                    If iSelectedLine < LBound(m_arrLines, 2) Then
                        iNextLine = LBound(m_arrLines, 2) ' start looking at beginning
                    Else
                        iNextLine = iSelectedLine + 1
                        If iNextLine > UBound(m_arrLines, 2) Then
                            iNextLine = LBound(m_arrLines, 2) ' start looking at beginning
                        End If
                    End If
                   
                    ' DETERMINE IF A LINE SEGMENT BEGINS OR ENDS HERE
                    iFirstLine = iNextLine
                    iFoundLine = LBound(m_arrLines, 2) - 1 ' none selected
                    Do
                        If m_arrLines(iSelectedObject, iNextLine).IsEnabled = TRUE Then
                            If iNextLine <> iSelectedLine Then
                                ' CHECK START POINT NEXT
                                If m_arrLines(iSelectedObject, iNextLine).x1 = iPolarX Then
                                    If m_arrLines(iSelectedObject, iNextLine).y1 = iPolarY Then
                                        ' MATCHES START POINT
                                        'DebugPrint "found (" + cstr$(iPolarX) + "," + cstr$(iPolarY) + ") = BEGIN POINT " + ColorName$(m_arrLines(iSelectedObject, iNextLine).color)
                                        iFoundLine = iNextLine
                                        iEditWhich = 1 ' remember we selected start point x1,y1
                                        Exit Do
                                    End If
                                End If
                                ' CHECK END POINT NEXT
                                If m_arrLines(iSelectedObject, iNextLine).x2 = iPolarX Then
                                    If m_arrLines(iSelectedObject, iNextLine).y2 = iPolarY Then
                                        ' MATCHES END POINT
                                        'DebugPrint "found (" + cstr$(iPolarX) + "," + cstr$(iPolarY) + ") = END   POINT " + ColorName$(m_arrLines(iSelectedObject, iNextLine).color)
                                        iFoundLine = iNextLine
                                        iEditWhich = 2 ' remember we selected end point x2,y2
                                        Exit Do
                                    End If
                                End If
                            End If
                        End If
                       
                        ' MOVE TO NEXT LINE
                        iNextLine = iNextLine + 1
                        If iNextLine > UBound(m_arrLines, 2) Then
                            iNextLine = LBound(m_arrLines, 2) ' start looking at beginning
                        End If
                       
                        ' IF WE'RE BACK TO THE START THEN NO MORE FOUND, EXIT
                        If iNextLine = iFirstLine Then
                            Exit Do
                        End If
                    Loop
                   
                    ' DID WE FIND ONE?
                    If iFoundLine >= LBound(m_arrLines, 2) Then
                        If iFoundLine <= UBound(m_arrLines, 2) Then
                            ' CHANGE SELECTION
                            iSelectedLine = iFoundLine
                           
                            ' REMEMBER WHAT POINT WE CLICKED
                            iEditX = iPolarX ' remember selected x
                            iEditY = iPolarY ' remember selected y
                        Else
                            ' IS THIS NEEDED?
                            iSelectedLine = LBound(m_arrLines, 2) - 1 ' none selected
                        End If
                    Else
                        ' IS THIS NEEDED?
                        iSelectedLine = LBound(m_arrLines, 2) - 1 ' none selected
                    End If
                    ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
                    ' END CLICK LEFT PANE / ANCHOR POINTS
                    ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
                End If
               
                ' REMEMBER
                bOldLeftClick = TRUE
            End If
        Else
            bOldLeftClick = FALSE
        End If
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        ' END LEFT CLICK
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
       
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        ' BEGIN RIGHT CLICK
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        bRightClick = _MouseButton(2)
        If bRightClick Then
            If bOldRightClick = FALSE Then
                If bEnableSelect = TRUE Then
                    ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
                    ' BEGIN RIGHT-CLICK RIGHT PANE / SELECT OBJECT
                    ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
                    ' USER CLICKED RIGHT SIDE OF SCREEN FOR SELECT OBJECT:
                    ' (NO RIGHT-CLICK ACTIONS YET IN RIGHT PANE)
                    ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
                    ' END RIGHT-CLICK RIGHT PANE / SELECT OBJECT
                    ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
                Else
                    ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
                    ' BEGIN RIGHT-CLICK LEFT PANE / ANCHOR POINTS
                    ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
                   
                    ' IS A LINE SEGMENT SELECTED?
                    If iSelectedLine >= LBound(m_arrLines, 2) Then
                        If iSelectedLine <= UBound(m_arrLines, 2) Then
                            ' DID USER RIGHT-CLICK SELECTED ENDPOINT?
                            If (iPolarX = iEditX) And (iPolarY = iEditY) Then
                                ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
                                ' BEGIN DELETE THE LINE
                                ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
                                ' STARTING WITH SELECTED, OVERWRITE EACH WITH THE ONE AFTER
                                If iSelectedLine < UBound(m_arrLines, 2) Then
                                    For iNextLine = iSelectedLine To (UBound(m_arrLines, 2) - 1)
                                        m_arrLines(iSelectedObject, iNextLine).x1 = m_arrLines(iSelectedObject, iNextLine + 1).x1
                                        m_arrLines(iSelectedObject, iNextLine).y1 = m_arrLines(iSelectedObject, iNextLine + 1).y1
                                        m_arrLines(iSelectedObject, iNextLine).x2 = m_arrLines(iSelectedObject, iNextLine + 1).x2
                                        m_arrLines(iSelectedObject, iNextLine).y2 = m_arrLines(iSelectedObject, iNextLine + 1).y2
                                        m_arrLines(iSelectedObject, iNextLine).color = m_arrLines(iSelectedObject, iNextLine + 1).color
                                        m_arrLines(iSelectedObject, iNextLine).IsLast = m_arrLines(iSelectedObject, iNextLine + 1).IsLast
                                        m_arrLines(iSelectedObject, iNextLine).IsEnabled = m_arrLines(iSelectedObject, iNextLine + 1).IsEnabled
                                    Next iNextLine
                                Else
                                    ' MAKE NEXT-TO-LAST THE LAST ONE
                                    iNextLine = iSelectedLine - 1
                                    m_arrLines(iSelectedObject, iNextLine).IsLast = TRUE
                                End If
                                ' CLEAR THE LAST ONE
                                iNextLine = UBound(m_arrLines, 2)
                                m_arrLines(iSelectedObject, iNextLine).x1 = cPolarNone
                                m_arrLines(iSelectedObject, iNextLine).y1 = cPolarNone
                                m_arrLines(iSelectedObject, iNextLine).x2 = cPolarNone
                                m_arrLines(iSelectedObject, iNextLine).y2 = cPolarNone
                                m_arrLines(iSelectedObject, iNextLine).color = cEmpty
                                m_arrLines(iSelectedObject, iNextLine).IsLast = FALSE
                                m_arrLines(iSelectedObject, iNextLine).IsEnabled = FALSE
                                ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
                                ' END DELETE THE LINE
                                ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
                            Else
                                ' NO, MOVE ENDPOINT TO NEW COORDINATE
                                If iEditWhich = 1 Then
                                    m_arrLines(iSelectedObject, iSelectedLine).x1 = iPolarX
                                    m_arrLines(iSelectedObject, iSelectedLine).y1 = iPolarY
                                ElseIf iEditWhich = 2 Then
                                    m_arrLines(iSelectedObject, iSelectedLine).x2 = iPolarX
                                    m_arrLines(iSelectedObject, iSelectedLine).y2 = iPolarY
                                End If
                            End If
                        End If
                    End If
                   
                    ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
                    ' END RIGHT-CLICK LEFT PANE / ANCHOR POINTS
                    ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
                End If
               
                ' REMEMBER
                bOldRightClick = TRUE
            End If
        Else
            bOldRightClick = FALSE
        End If
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        ' END RIGHT CLICK
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
       
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        ' READ MOUSE
        Do While _MouseInput
            ' READ MOUSE WHEEL:
            'iY2 = iY2 + (_MouseWheel * _FontHeight) ' -1 up, 0 no movement, 1 down
        Loop
       
        ' ================================================================================================================================================================
        ' BEGIN PROCESS KEYBOARD 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
                sCommand = ""
            End If
        End If
       
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        ' BEGIN READY TO ACCEPT MORE INPUT?
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        If iLastKey < 1 Then
           
            ' DID PLAYER PRESS ANY KEYS WE KNOW?
            If _Button(KeyCode_C%) Then
                sCommand = "c"
                iLastKey = KeyCode_C%
            ElseIf _Button(KeyCode_I%) Then
                sCommand = "i"
                iLastKey = KeyCode_I%
            ElseIf _Button(KeyCode_U%) Then
                sCommand = "u"
                iLastKey = KeyCode_U%
            ElseIf _Button(KeyCode_Del%) Then
                sCommand = "delete"
                iLastKey = KeyCode_Del%
            'ElseIf _Button(KeyCode_Ins%) Then
            '    sCommand = "insert"
            '    iLastKey = KeyCode_Ins%
            ElseIf _Button(KeyCode_Escape%) Then
                if bFirstClick = FALSE then
                    sCommand = "deselect"
                elseIf iSelectedLine >= LBound(m_arrLines, 2) Then
                    sCommand = "deselect"
                Else
                    sCommand = "esc"
                End If
                iLastKey = KeyCode_Escape%
            ElseIf _Button(KeyCode_Left%) Then
                sCommand = "left"
                'iLastKey = KeyCode_Left%
                iLastKey = -1 ' allow repeat for this key
            ElseIf _Button(KeyCode_Right%) Then
                sCommand = "right"
                'iLastKey = KeyCode_Right%
                iLastKey = -1 ' allow repeat for this key
            ElseIf _Button(KeyCode_Up%) Then
                sCommand = "up"
                'iLastKey = KeyCode_Up%
                iLastKey = -1 ' allow repeat for this key
            ElseIf _Button(KeyCode_Down%) Then
                sCommand = "down"
                'iLastKey = KeyCode_Down%
                iLastKey = -1 ' allow repeat for this key
            Else
                iLastKey = 0
                sCommand = ""
            End If
           
            ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
            ' BEGIN IF USER DID PRESS A KEY WE KNOW, PROCESS INPUT
            ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
            If iLastKey <> 0 Then
                If sCommand = "" Then
                    ' (DO NOTHING)
                ElseIf sCommand = "c" Then
                    ' SELECT NEXT COLOR (CYCLE THROUGH COLORS)
                    iColorIndex = iColorIndex + 1
                    If iColorIndex > UBound(m_arrColor) Then
                        iColorIndex = LBound(m_arrColor)
                    End If
                ElseIf sCommand = "i" Then
                    ' "EYEDROPPER" COPY COLOR FROM CURRENT LINE
                    If iSelectedLine >= LBound(m_arrLines, 2) Then
                        iColorIndex = LBound(m_arrColor)
                        For iIndex = LBound(m_arrColor) To UBound(m_arrColor)
                            If m_arrColor(iIndex) = m_arrLines(iSelectedObject, iSelectedLine).color Then
                                iColorIndex = iIndex
                                Exit For
                            End If
                        Next iIndex
                    End If
                ElseIf sCommand = "u" Then
                    ' UPDATE COLOR OF CURRENT LINE
                    If iSelectedLine >= LBound(m_arrLines, 2) Then
                        m_arrLines(iSelectedObject, iSelectedLine).color = m_arrColor(iColorIndex)
                    End If
                   
                   
                   
                   
                'ElseIf sCommand = "insert" Then
                '    ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
                '    ' BEGIN INSERT LINE
                '    ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
                '    iLineCount = 0
                '    For iNextLine = LBound(m_arrLines, 2) To UBound(m_arrLines, 2)
                '        If m_arrLines(iSelectedObject, iNextLine).IsEnabled = TRUE Then
                '            iLineCount = iLineCount + 1
                '        End If
                '    Next iNextLine
                '    ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
                '    ' END INSERT LINE
                '    ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
               
                ElseIf sCommand = "delete" Then
                    ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
                    ' BEGIN DELETE THE LINE
                    ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
                    ' STARTING WITH SELECTED, OVERWRITE EACH WITH THE ONE AFTER
                    'If iSelectedLine < UBound(m_arrLines, 2) Then
                    '    For iNextLine = iSelectedLine To (UBound(m_arrLines, 2) - 1)
                    '        m_arrLines(iSelectedObject, iNextLine).x1 = m_arrLines(iSelectedObject, iNextLine + 1).x1
                    '        m_arrLines(iSelectedObject, iNextLine).y1 = m_arrLines(iSelectedObject, iNextLine + 1).y1
                    '        m_arrLines(iSelectedObject, iNextLine).x2 = m_arrLines(iSelectedObject, iNextLine + 1).x2
                    '        m_arrLines(iSelectedObject, iNextLine).y2 = m_arrLines(iSelectedObject, iNextLine + 1).y2
                    '        m_arrLines(iSelectedObject, iNextLine).color = m_arrLines(iSelectedObject, iNextLine + 1).color
                    '        m_arrLines(iSelectedObject, iNextLine).IsLast = m_arrLines(iSelectedObject, iNextLine + 1).IsLast
                    '        m_arrLines(iSelectedObject, iNextLine).IsEnabled = m_arrLines(iSelectedObject, iNextLine + 1).IsEnabled
                    '    Next iNextLine
                    'Else
                    '    ' MAKE NEXT-TO-LAST THE LAST ONE
                    '    iNextLine = iSelectedLine - 1
                    '    m_arrLines(iSelectedObject, iNextLine).IsLast = TRUE
                    'End If
                    '' CLEAR THE LAST ONE
                    'iNextLine = UBound(m_arrLines, 2)
                    'm_arrLines(iSelectedObject, iNextLine).x1 = cPolarNone
                    'm_arrLines(iSelectedObject, iNextLine).y1 = cPolarNone
                    'm_arrLines(iSelectedObject, iNextLine).x2 = cPolarNone
                    'm_arrLines(iSelectedObject, iNextLine).y2 = cPolarNone
                    'm_arrLines(iSelectedObject, iNextLine).color = cEmpty
                    'm_arrLines(iSelectedObject, iNextLine).IsLast = FALSE
                    'm_arrLines(iSelectedObject, iNextLine).IsEnabled = FALSE
                    
                    If iSelectedLine >= LBound(m_arrLines, 2) And iSelectedLine < UBound(m_arrLines, 2) Then
                        m_arrLines(iSelectedObject, iSelectedLine).x1 = cPolarNone
                        m_arrLines(iSelectedObject, iSelectedLine).y1 = cPolarNone
                        m_arrLines(iSelectedObject, iSelectedLine).x2 = cPolarNone
                        m_arrLines(iSelectedObject, iSelectedLine).y2 = cPolarNone
                        m_arrLines(iSelectedObject, iSelectedLine).color = cEmpty
                        m_arrLines(iSelectedObject, iSelectedLine).IsLast = FALSE
                        m_arrLines(iSelectedObject, iSelectedLine).IsEnabled = FALSE
                    End If
                    ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
                    ' END DELETE THE LINE
                    ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
                ElseIf sCommand = "deselect" Then
                    iSelectedLine = LBound(m_arrLines, 2) - 1 ' none selected
                    bFirstClick = TRUE
                    sStatus = ""
                    
                ElseIf sCommand = "left" Then
                    oPreview.dx = oPreview.dx - 1
                    If oPreview.dx < -10 Then oPreview.dx = -10
                ElseIf sCommand = "right" Then
                    oPreview.dx = oPreview.dx + 1
                    If oPreview.dx > 10 Then oPreview.dx = 10
                ElseIf sCommand = "up" Then
                    oPreview.dy = oPreview.dy - 1
                    If oPreview.dy < -10 Then oPreview.dy = -10
                ElseIf sCommand = "down" Then
                    oPreview.dy = oPreview.dy + 1
                    If oPreview.dy > 10 Then oPreview.dy = 10
                   
                ElseIf sCommand = "esc" Then
                    Exit Do
                End If
            End If ' iLastKey <> 0
            ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
            ' END IF USER DID PRESS A KEY WE KNOW, PROCESS INPUT
            ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        End If ' IF iLastKey = 0
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        ' END READY TO ACCEPT MORE INPUT?
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
       
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        ' CLEAR KEYBOARD BUFFER
        _KeyClear ': _Delay 2
        ' ================================================================================================================================================================
        ' END PROCESS KEYBOARD INPUT
        ' ================================================================================================================================================================
       
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
        ' BEGIN UPDATE SCREEN
       
        ' COPY LAYERS TO SCREEN
        '_Dest 0: Cls , cBlack
        If imgObjects& < -1 Then
            _PutImage , imgObjects&, 0
        End If
       
        ' UPDATE THE SCREEN
        _Display
       
        ' END UPDATE SCREEN
        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
       
    Loop
    _KeyClear
   
    ' RESET MOUSE
    _MouseShow "default": _Delay 0.5
   
    ' CLEAR IMAGES
    Screen 0
    If imgObjects& < -1 Then _FreeImage imgObjects&
    If imgTemp& < -1 Then _FreeImage imgTemp&
   
    ' RETURN TO AUTODISPLAY
    _AutoDisplay
   
    ' Screen 0 set at end of program up top.
    Screen 0
   
End Sub ' EditVectorObject1

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

Sub DrawVectorObjectTest1
    Dim RoutineName As String: RoutineName = "DrawVectorObjectTest1"
    Dim iFPS As Integer: iFPS = cFPS
    Dim iLoop As Integer
    Dim iObject As Integer
    Dim iLayer As Integer
    Dim iLine As Integer
    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 iStyleCountMax As Integer: iStyleCountMax = iFPS \ 12 ' change 4x a second
    Dim iStyleCountNext As Integer: iStyleCountNext = 0
    Dim iLineStyleIndex As Integer
    Dim lngLineStyle ' line style
    Dim lngDashedLineStyle ' line style for other objects
    Dim lngSolidLineStyle ' selected object's line style
    Dim iNumStars As Integer
    Dim iValue As Integer
    Dim iMinValue As Integer
    Dim iMaxValue As Integer
    Dim iStarLoop As Integer
    Dim x1%
    Dim x2%
    Dim y1%
    Dim y2%
    Dim imgStars& ' used for drawing background
    Dim imgText& ' used for drawing text
    Dim imgObjects& ' used for drawing objects
    Dim imgTemp& ' temporary drawing area
   
    ' =============================================================================
    ' INITIALIZE
    InitializeRandom
    Screen _NewImage(iMaxX, iMaxY, 32) ' 100 text columns x 40 text rows
   
    ' USE LATER FOR DRAWING LAYERS:
    imgStars& = _NewImage(iMaxX, iMaxY, 32) ' background stars
    imgText& = _NewImage(iMaxX, iMaxY, 32) ' text
    imgObjects& = _NewImage(iMaxX, iMaxY, 32) ' frontground objects
    imgTemp& = _NewImage(iMaxX, iMaxY, 32) ' temporary
   
    ' =============================================================================
    ' START NEW GAME
    Do
        _KeyClear

        ' CONFIGURE PRINTING FOR _PrintString
        _PrintMode _FillBackground
        '_PrintMode _KEEPBACKGROUND

        ' INIT OBJECT DEFINITIONS
        InitVectorObjects
       
        ' INIT VARS
        sKey = ""
       
        ' PLACE OBJECTS
        iX = 0: iY = 0
        iValue = UBound(m_arrObject)
        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
            m_arrObject(iObject).z = iValue
            m_arrObject(iObject).FillColor = cBlack
           
            iX = iX + 200
            If iX > 800 Then
                iX = 0
                iY = iY + 200
                If iY > 640 Then
                    iY = 0
                End If
            End If
            iValue = iValue - 1
        Next iObject
       
        ' INIT OBJECT Z-ORDER ARRAY
        ReDim m_arrOrder(LBound(m_arrObject) To UBound(m_arrObject)) As Integer
        For iObject = LBound(m_arrObject) To UBound(m_arrObject)
            m_arrOrder(m_arrObject(iObject).z) = iObject
        Next iObject
       
        ' SAVE LINE STYLE SEQUENCE
        iLoop = 0
        iLoop = iLoop + 1: m_arrLineStyle(iLoop) = 61680 ' 1111000011110000
        iLoop = iLoop + 1: m_arrLineStyle(iLoop) = 30840 ' 0111100001111000
        iLoop = iLoop + 1: m_arrLineStyle(iLoop) = 15420 ' 0011110000111100
        iLoop = iLoop + 1: m_arrLineStyle(iLoop) = 7710 ' 0001111000011110
        iLoop = iLoop + 1: m_arrLineStyle(iLoop) = 3855 ' 0000111100001111
        iLoop = iLoop + 1: m_arrLineStyle(iLoop) = 34695 ' 1000011110000111
        iLoop = iLoop + 1: m_arrLineStyle(iLoop) = 50115 ' 1100001111000011
        iLoop = iLoop + 1: m_arrLineStyle(iLoop) = 57825 ' 1110000111100001
        iLineStyleIndex = LBound(m_arrLineStyle)
        lngDashedLineStyle = m_arrLineStyle(iLineStyleIndex)
        lngSolidLineStyle = 65535
       
        ' SAVE COLORS FOR EACH OBJECT (FROM FIRST LINE SEGMENT) TO ARRAY FOR TEXT
        For iObject = LBound(m_arrObject) To UBound(m_arrObject)
            iLine = LBound(m_arrLines, 2)
            m_arrObject(iObject).PreviewColor = m_arrLines(iObject, iLine).color
        Next iObject
       
        ' SAVE COLORS FOR STARS AND GRAYSCALE OBJECTS
        AddGrayscaleColors m_arrGrayColor()
       
        ' -----------------------------------------------------------------------------
        ' PLACE STARS RANDOMLY
        iNumStars = RandomNumber%(cMinStars, cMaxStars)
        ReDim m_arrStars(1 To iNumStars) As StarType
        For iLoop = 1 To iNumStars
            m_arrStars(iLoop).x = RandomNumber%(iMinX, iMaxX)
            m_arrStars(iLoop).y = RandomNumber%(iMinY, iMaxY)
            m_arrStars(iLoop).ColorIndex = RandomNumber%(LBound(m_arrGrayColor), UBound(m_arrGrayColor))
           
            ' Assign a width 1-3 (with different probability for each)
            iValue = RandomNumber%(1, 100)
            If iValue > 98 Then
                m_arrStars(iLoop).MinWidth = RandomNumber%(2, 3)
                m_arrStars(iLoop).MaxWidth = 3
            ElseIf iValue > 85 Then
                m_arrStars(iLoop).MinWidth = RandomNumber%(1, 2)
                m_arrStars(iLoop).MaxWidth = 2
            Else
                m_arrStars(iLoop).MinWidth = RandomNumber%(0, 1)
                m_arrStars(iLoop).MaxWidth = 1
            End If
            ' Set initial width to normal (MaxWidth)
            m_arrStars(iLoop).width = m_arrStars(iLoop).MaxWidth
           
            ' Determine how quickly size changes
            ' Anywhere between 1/30 second and 1 seconds
            iMinValue = iFPS \ 30
            iMaxValue = iFPS
            m_arrStars(iLoop).MaxWidthCount = RandomNumber%(iMinValue, iMaxValue)
            m_arrStars(iLoop).WidthCounter = 0
           
            ' Determine how long size is changed
            ' Anywhere between 1/100 second and 1/50 seconds
            iMinValue = iFPS \ 100
            iMaxValue = iFPS \ 50
            m_arrStars(iLoop).MaxBigCount = RandomNumber%(iMinValue, iMaxValue)
            m_arrStars(iLoop).BigCounter = 0
           
            ' Determine how quickly they twinkle
            ' Anywhere between 1/120 second and 1/20 seconds
            iMinValue = iFPS \ 120
            iMaxValue = iFPS \ 20
            m_arrStars(iLoop).MaxTwinkCount = RandomNumber%(iMinValue, iMaxValue)
            m_arrStars(iLoop).TwinkleCounter = 0
        Next iLoop
       
        ' ================================================================================================================================================================
        ' BEGIN MAIN LOOP
        While TRUE = TRUE
           
            ' CLEAR OBJECTS LAYER
            _Dest imgObjects&: Cls , cEmpty
           
            ' MOVE AND ADD ENABLED OBJECTS (IN STACKING ORDER)
            For iLayer = UBound(m_arrOrder) To LBound(m_arrOrder) Step -1
                ' Get next object
                iObject = m_arrOrder(iLayer)
               
                ' 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 - cSpeed
                            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 + cSpeed
                            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 - cSpeed
                            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 + cSpeed
                            If m_arrObject(iObject).y > iMaxY Then
                                m_arrObject(iObject).y = iMinY
                            End If
                        End If
                    End If
                   
                    ' Clear temporary (layer and draw on it
                    _Dest imgTemp&: Cls , cEmpty
                   
                    ' Draw object's line segments
                    For iLine = LBound(m_arrLines, 2) To UBound(m_arrLines, 2)
                        If m_arrLines(iObject, iLine).IsEnabled = TRUE Then
                            If m_arrLines(iObject, iLine).IsLast = FALSE Then
                                'Line (x1%, y1%)-(x2%, y2%), arrColor(arrStars(iLoop).ColorIndex), BF, lngLineStyle
                                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, , lngSolidLineStyle
                            Else
                                Exit For
                            End If
                        End If
                    Next iLine
                   
                    ' Draw fill color if not transparent
                    If m_arrObject(iObject).FillColor <> cEmpty Then
                        ' Fill in current object with its fill color...
                        'PAINT [STEP] (column%, row%), fillColor[, borderColor%]
                        Paint (m_arrObject(iObject).x, m_arrObject(iObject).y), _
                            m_arrObject(iObject).FillColor, m_arrObject(iObject).PreviewColor
                    End If
                   
                    ' Make other objects appear drawn with a dashed line
                    If iObject <> iWhich Then
                        ' Outline with a dashed line
                        For iLine = LBound(m_arrLines, 2) To UBound(m_arrLines, 2)
                            If m_arrLines(iObject, iLine).IsEnabled = TRUE Then
                                If m_arrLines(iObject, iLine).IsLast = FALSE Then
                                    'Line (x1%, y1%)-(x2%, y2%), arrColor(arrStars(iLoop).ColorIndex), BF, lngLineStyle
                                    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) _
                                        , _
                                        cBlack, , lngDashedLineStyle
                                Else
                                    Exit For
                                End If
                            End If
                        Next iLine
                    End If
                   
                    ' Add new object to objects layer
                    '_PUTIMAGE , sourceHandle&, destHandle& 'size full source to fit full destination area
                    _PutImage , imgTemp&, imgObjects&
                   
                End If
            Next iLayer
           
            ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
            ' BEGIN STARS
           
            ' Twinkle twinkle little stars
            _Dest imgStars&: Cls , cEmpty
            For iStarLoop = LBound(m_arrStars) To UBound(m_arrStars)
                ' increment twinkle counter
                m_arrStars(iStarLoop).TwinkleCounter = m_arrStars(iStarLoop).TwinkleCounter + 1
               
                ' is it time to twinkle the color?
                If m_arrStars(iStarLoop).TwinkleCounter > m_arrStars(iStarLoop).MaxTwinkCount Then
                    m_arrStars(iStarLoop).TwinkleCounter = 0 ' reset counter
                    m_arrStars(iStarLoop).ColorIndex = m_arrStars(iStarLoop).ColorIndex + 1 ' increment color
                    If m_arrStars(iStarLoop).ColorIndex > UBound(m_arrGrayColor) Then
                        m_arrStars(iStarLoop).ColorIndex = LBound(m_arrGrayColor) ' reset color
                    End If
                End If
               
                ' increment width counter
                If m_arrStars(iStarLoop).BigCounter = 0 Then
                    m_arrStars(iStarLoop).WidthCounter = m_arrStars(iStarLoop).WidthCounter + 1
                   
                    ' is it time to fluctuate the width
                    If m_arrStars(iStarLoop).WidthCounter > m_arrStars(iStarLoop).MaxWidthCount Then
                        m_arrStars(iStarLoop).WidthCounter = 0 ' reset counter
                        m_arrStars(iStarLoop).BigCounter = 1 ' start big counter
                        m_arrStars(iStarLoop).width = m_arrStars(iStarLoop).MinWidth ' twinkle width
                    Else
                        m_arrStars(iStarLoop).width = m_arrStars(iStarLoop).MaxWidth ' normal width
                    End If
                Else
                    ' increment big counter
                    m_arrStars(iStarLoop).BigCounter = m_arrStars(iStarLoop).BigCounter + 1
                   
                    ' is it time to return to normal size?
                    If m_arrStars(iStarLoop).BigCounter > m_arrStars(iStarLoop).MaxBigCount Then
                        m_arrStars(iStarLoop).BigCounter = 0 ' reset counter
                        m_arrStars(iStarLoop).width = m_arrStars(iStarLoop).MaxWidth ' normal width
                    End If
                End If
               
                ' get size
                x1% = m_arrStars(iStarLoop).x: x2% = x1% + m_arrStars(iStarLoop).width
                y1% = m_arrStars(iStarLoop).y: y2% = y1% + m_arrStars(iStarLoop).width
               
                ' (re)draw it
                Line (x1%, y1%)-(x2%, y2%), m_arrGrayColor(m_arrStars(iStarLoop).ColorIndex), BF
            Next iStarLoop
            ' END STARS
            ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
           
            ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
            ' SHOW TEXT
            _Dest imgText&: Cls , cEmpty
            DrawText sKey, iWhich
            ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
           
            ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
            ' BEGIN UPDATE SCREEN
           
            ' COPY LAYERS TO SCREEN
            _Dest 0: Cls , cBlack
            If imgStars& < -1 Then
                _PutImage , imgStars&, 0
            End If
            If imgText& < -1 Then
                _PutImage , imgText&, 0
            End If
            If imgObjects& < -1 Then
                _PutImage , imgObjects&, 0
            End If
           
            ' UPDATE THE SCREEN
            _Display
           
            ' END UPDATE SCREEN
            ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
           
            ' 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

            ' CYCLE LINE STYLE FOR ACTIVE OBJECT
            iStyleCountNext = iStyleCountNext + 1 ' increment line style counter
            If iStyleCountNext > iStyleCountMax Then
                iStyleCountNext = 0
                iLineStyleIndex = iLineStyleIndex + 1
                If iLineStyleIndex > UBound(m_arrLineStyle) Then
                    iLineStyleIndex = LBound(m_arrLineStyle)
                End If
                lngDashedLineStyle = m_arrLineStyle(iLineStyleIndex)
            End If

            ' CLEAR KEYBOARD BUFFER
            _KeyClear

            ' CONTROL GAME SPEED
            _Limit iFPS
        Wend
        ' END MAIN LOOP
        ' ================================================================================================================================================================

        ' 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
   
    ' CLEAR IMAGES
    Screen 0
    If imgStars& < -1 Then _FreeImage imgStars&
    If imgText& < -1 Then _FreeImage imgText&
    If imgObjects& < -1 Then _FreeImage imgObjects&
    If imgTemp& < -1 Then _FreeImage imgTemp&
   
    ' RETURN TO AUTODISPLAY
    _AutoDisplay

End Sub ' DrawVectorObjectTest1

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

' Receives:
' Long iInput1 = value (0-65535) to conver to binary

' Returns a 16 character string of "1" and "0"
' (a 16-bit binary representation of iInput1)

Function BinaryStringFromLong$ (iInput1 As Long)
    Dim sBinary As String
    Dim iInput As Long
    Dim iLoop As Integer
    Dim iNextValue As Long

    sBinary = ""
    iInput = iInput1
    If iInput >= 0 Then
        For iLoop = 15 To 0 Step -1
            iNextValue = 2 ^ iLoop
            If (iInput \ iNextValue) > 0 Then
                sBinary = sBinary + "1"
            Else
                sBinary = sBinary + "0"
            End If
            iInput = iInput Mod iNextValue
        Next iLoop
    End If

    BinaryStringFromLong$ = sBinary
End Function ' BinaryStringFromLong$

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

' Receives:
' String sBitPattern = 16 character string of "1" and "0"
'                      (a 16-bit binary representation)

' Returns the bit pattern converted to a long integer.

Function LongFromBinaryString& (sBitPattern As String)
    Dim sInput As String: sInput = sBitPattern
    Dim iLoop As Integer
    Dim MyLong As Long

    MyLong = 0

    If Len(sInput) >= 16 Then
        For iLoop = 0 To 15
            If Mid$(sInput, 16 - iLoop, 1) = "1" Then
                MyLong = MyLong + (2 ^ iLoop)
            End If
        Next iLoop
    End If

    LongFromBinaryString& = MyLong
End Function ' LongFromBinaryString&

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

' future versions will pull this data from an editable 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

    ' CLEAR OUT EXISTING
    For iObject = LBound(m_arrObject) To UBound(m_arrObject)
        m_arrObject(iObject).IsEnabled = FALSE
    Next iObject
    For iObject = LBound(m_arrLines, 1) To UBound(m_arrLines, 1)
        For iLine = LBound(m_arrLines, 2) To UBound(m_arrLines, 2)
            m_arrLines(iObject, iLine).x1 = 0
            m_arrLines(iObject, iLine).y1 = 0
            m_arrLines(iObject, iLine).x2 = 0
            m_arrLines(iObject, iLine).y2 = 0
            m_arrLines(iObject, iLine).color = cEmpty
            m_arrLines(iObject, iLine).IsLast = TRUE
            m_arrLines(iObject, iLine).IsEnabled = FALSE
        Next iLine
    Next iObject
   
    ' INITIALIZE FROM SAVED
    Restore VectorData
    iObject = 1
    iLine = 1
    bEnabled = TRUE
    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 ' NO MORE DATA AT ALL
            If iLine <= UBound(m_arrLines, 2) Then
                m_arrLines(iObject, iLine).IsEnabled = FALSE
                m_arrLines(iObject, iLine).IsLast = TRUE
            else
                m_arrLines(iObject, UBound(m_arrLines, 2)).IsLast = TRUE
            end if
            m_arrObject(iObject).IsEnabled = TRUE
            Exit For
            
        ElseIf b1 = -254 Then ' NO MORE DATA FOR THIS OBJECT
            If iLine <= UBound(m_arrLines, 2) Then
                m_arrLines(iObject, iLine).IsEnabled = FALSE
                m_arrLines(iObject, iLine).IsLast = TRUE
            else
                m_arrLines(iObject, UBound(m_arrLines, 2)).IsLast = TRUE
            end if
            m_arrObject(iObject).IsEnabled = TRUE
            iObject = iObject + 1
            iLine = 1
            If iObject > UBound(m_arrLines, 1) Then Exit For
        Else
            If iLine <= UBound(m_arrLines, 2) Then
                m_arrLines(iObject, iLine).IsEnabled = TRUE
                m_arrLines(iObject, iLine).IsLast = FALSE
                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)
                iLine = iLine + 1
            End If
            'If iLine > UBound(m_arrLines, 2) Then Exit For
        End If
    Next iLoop%
   
    VectorData:
   
    '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,255,0,0
    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 = yellow
    Data 9,-62,60,-21,255,255,0
    Data 60,-21,62,-3,255,255,0
    Data 62,-3,24,13,255,255,0
    Data 24,13,53,34,255,255,0
    Data 53,34,38,55,255,255,0
    Data 38,55,20,40,255,255,0
    Data 20,40,-37,61,255,255,0
    Data -37,61,-63,15,255,255,0
    Data -63,15,-57,-24,255,255,0
    Data -57,-24,-24,-24,255,255,0
    Data -24,-24,-38,-45,255,255,0
    Data -38,-45,9,-62,255,255,0
    Data 0,0,0,0,-254,-254,-254

    'objmouse = white
    Data 0,-10,6,3,255,255,255
    Data 6,3,1,2,255,255,255
    Data 1,2,1,10,255,255,255
    Data 1,10,-1,10,255,255,255
    Data -1,10,-1,2,255,255,255
    Data -1,2,-6,3,255,255,255
    Data -6,3,0,-10,255,255,255
    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
    'Data 0,18,0,18,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,-254,-254,-254
    'Data 0,0,0,0,-255,-255,-255
   
   
   
   
    'test object
    Data -31,-31,-31,31,255,0,0
    Data -31,31,31,31,255,0,0
    Data 31,31,31,-31,255,0,0
    Data 31,-31,-31,-31,255,0,0
    Data 0,0,0,0,-254,-254,-254
   
    'test object
    Data -31,-31,-31,31,0,0,255
    Data -31,31,31,31,0,0,255
    Data 31,31,31,-31,0,0,255
    Data 31,-31,-31,-31,0,0,255
    Data 0,0,0,0,-254,-254,-254
   
   
   
   
   
    '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 = yellow
    Data 9,-62,60,-21,255,255,0
    Data 60,-21,62,-3,255,255,0
    Data 62,-3,24,13,255,255,0
    Data 24,13,53,34,255,255,0
    Data 53,34,38,55,255,255,0
    Data 38,55,20,40,255,255,0
    Data 20,40,-37,61,255,255,0
    Data -37,61,-63,15,255,255,0
    Data -63,15,-57,-24,255,255,0
    Data -57,-24,-24,-24,255,255,0
    Data -24,-24,-38,-45,255,255,0
    Data -38,-45,9,-62,255,255,0
    Data 0,0,0,0,-254,-254,-254

    'objmouse = white
    Data 0,-10,6,3,255,255,255
    Data 6,3,1,2,255,255,255
    Data 1,2,1,10,255,255,255
    Data 1,10,-1,10,255,255,255
    Data -1,10,-1,2,255,255,255
    Data -1,2,-6,3,255,255,255
    Data -6,3,0,-10,255,255,255
    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
    'Data 0,18,0,18,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,-254,-254,-254
    'Data 0,0,0,0,-255,-255,-255
   
    'test object with max lines
    Data -63,-63,63,-63,255,0,0
    Data 63,-63,63,63,0,255,0
    Data 63,63,-63,63,0,0,255
    Data -63,63,-63,-63,255,255,0
    Data -59,-59,59,-59,255,0,0
    Data 59,-59,59,59,0,255,0
    Data 59,59,-59,59,0,0,255
    Data -59,59,-59,-59,255,255,0
    Data -55,-55,55,-55,255,0,0
    Data 55,-55,55,55,0,255,0
    Data 55,55,-55,55,0,0,255
    Data -55,55,-55,-55,255,255,0
    Data -51,-51,51,-51,255,0,0
    Data 51,-51,51,51,0,255,0
    Data 51,51,-51,51,0,0,255
    Data -51,51,-51,-51,255,255,0
    Data -47,-47,47,-47,255,0,0
    Data 47,-47,47,47,0,255,0
    Data 47,47,-47,47,0,0,255
    Data -47,47,-47,-47,255,255,0
    Data -43,-43,43,-43,255,0,0
    Data 43,-43,43,43,0,255,0
    Data 43,43,-43,43,0,0,255
    Data -43,43,-43,-43,255,255,0
    Data -39,-39,39,-39,255,0,0
    Data 39,-39,39,39,0,255,0
    Data 39,39,-39,39,0,0,255
    Data -39,39,-39,-39,255,255,0
    Data -35,-35,35,-35,255,0,0
    Data 35,-35,35,35,0,255,0
    Data 35,35,-35,35,0,0,255
    Data -35,35,-35,-35,255,255,0
    Data 0,0,0,0,-254,-254,-254

    
    'test object
    Data -31,-31,-31,31,0,0,255
    Data -31,31,31,31,0,0,255
    Data 31,31,31,-31,0,0,255
    Data 31,-31,-31,-31,0,0,255
    Data 0,0,0,0,-254,-254,-254
   
   
   
   
   
   
   
   
   
    'test object
    Data -31,-31,-31,31,255,0,0
    Data -31,31,31,31,255,0,0
    Data 31,31,31,-31,255,0,0
    Data 31,-31,-31,-31,255,0,0
    Data 0,0,0,0,-254,-254,-254
   
    'objaster0 = deep purple square
    Data -63,-63,-63,63,96,0,255
    Data -63,63,63,63,96,0,255
    Data 63,63,63,-63,96,0,255
    Data 63,-63,-63,-63,96,0,255
    Data 0,0,0,0,-254,-254,-254
   
    'test object
    Data -31,-31,-31,31,0,0,255
    Data -31,31,31,31,0,0,255
    Data 31,31,31,-31,0,0,255
    Data 31,-31,-31,-31,0,0,255
    Data 0,0,0,0,-254,-254,-254
   
    '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 = yellow
    Data 9,-62,60,-21,255,255,0
    Data 60,-21,62,-3,255,255,0
    Data 62,-3,24,13,255,255,0
    Data 24,13,53,34,255,255,0
    Data 53,34,38,55,255,255,0
    Data 38,55,20,40,255,255,0
    Data 20,40,-37,61,255,255,0
    Data -37,61,-63,15,255,255,0
    Data -63,15,-57,-24,255,255,0
    Data -57,-24,-24,-24,255,255,0
    Data -24,-24,-38,-45,255,255,0
    Data -38,-45,9,-62,255,255,0
    Data 0,0,0,0,-254,-254,-254

    'objmouse = white
    Data 0,-10,6,3,255,255,255
    Data 6,3,1,2,255,255,255
    Data 1,2,1,10,255,255,255
    Data 1,10,-1,10,255,255,255
    Data -1,10,-1,2,255,255,255
    Data -1,2,-6,3,255,255,255
    Data -6,3,0,-10,255,255,255
    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
    'Data 0,18,0,18,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,-254,-254,-254
    'Data 0,0,0,0,-255,-255,-255
   
   
   
   
   
   
   
   
   
    '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 = yellow
    Data 9,-62,60,-21,255,255,0
    Data 60,-21,62,-3,255,255,0
    Data 62,-3,24,13,255,255,0
    Data 24,13,53,34,255,255,0
    Data 53,34,38,55,255,255,0
    Data 38,55,20,40,255,255,0
    Data 20,40,-37,61,255,255,0
    Data -37,61,-63,15,255,255,0
    Data -63,15,-57,-24,255,255,0
    Data -57,-24,-24,-24,255,255,0
    Data -24,-24,-38,-45,255,255,0
    Data -38,-45,9,-62,255,255,0
    Data 0,0,0,0,-254,-254,-254

    'objmouse = white
    Data 0,-10,6,3,255,255,255
    Data 6,3,1,2,255,255,255
    Data 1,2,1,10,255,255,255
    Data 1,10,-1,10,255,255,255
    Data -1,10,-1,2,255,255,255
    Data -1,2,-6,3,255,255,255
    Data -6,3,0,-10,255,255,255
    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
    'Data 0,18,0,18,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,-254,-254,-254
   
    'FINAL test object
    Data -31,-31,-31,31,255,255,255
    Data -31,31,31,31,255,255,255
    Data 31,31,31,-31,255,255,255
    Data 31,-31,-31,-31,255,255,255
    Data -31,-31,31,31,255,255,255
    Data 31,-31,-31,31,0,255,0
    Data 0,0,0,0,-255,-255,-255
    
End Sub ' InitVectorObjects

' ################################################################################################################################################################
' END SIMPLE VECTOR ENGINE @VEC
' ################################################################################################################################################################

' /////////////////////////////////////////////////////////////////////////////
' SHOW INSTRUMENTS + INSTRUCTIONS
' 800x600 = 40 rows x 100 columns

sub DrawText( _
    sKey as string, _
    iWhich as integer _
    )

    Dim iObject As Integer
    Dim RowNum As Integer
    Dim sFlag As String
    Dim iNum As Integer
   
    RowNum = 0

    Color cWhite, cEmpty
    RowNum = RowNum + 1: PrintAt RowNum, 1, m_sTitle
    RowNum = RowNum + 1
   
    Color cYellow, cEmpty
    RowNum = RowNum + 1: PrintAt RowNum, 1, "----------------------------------------"
    RowNum = RowNum + 1: PrintAt RowNum, 1, "Press 1-6 to select active object."
    RowNum = RowNum + 1: PrintAt RowNum, 1, "Arrow keys move active object."
    RowNum = RowNum + 1: PrintAt RowNum, 1, "Press ESC to quit"
    RowNum = RowNum + 1: PrintAt RowNum, 1, "----------------------------------------"
    RowNum = RowNum + 1

    ' SHOW OBJECTS
    Color cGray, cEmpty
    RowNum = RowNum + 1: PrintAt RowNum, 1, "----------------------------------------"

    iNum = 0
    For iObject = LBound(m_arrObject) To UBound(m_arrObject)
        iNum = iNum + 1
        If m_arrObject(iObject).IsEnabled = TRUE Then
            Color m_arrObject(iObject).PreviewColor, cEmpty
            If iObject = iWhich Then
                'Color cCyan, cEmpty
                sFlag = "-> "
            Else
                'Color cDodgerBlue, cEmpty
                sFlag = "   "
            End If
        Else
            Color cGray, cEmpty
            sFlag = "   "
        End If

        RowNum = RowNum + 1: PrintAt RowNum, 1, "" + _
            sFlag + _
            cstr$(iNum) + ". " + _
            "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) + ")" + _
            ""
    Next iObject

    Color cGray, cEmpty
    RowNum = RowNum + 1: PrintAt RowNum, 1, "----------------------------------------"

    RowNum = RowNum + 2

    '' SHOW ACTIVE OBJECT
    'Color cWhite
    'RowNum = RowNum + 1: PrintAt RowNum, 1, "Object #   : " + cstr$(iWhich)

    ' SHOW INPUT
    Color cLime, cEmpty
    RowNum = RowNum + 1: PrintAt RowNum, 1, "Controls   : " + RightPadString$(sKey, 10, " ") + "   "
   
End Sub ' DrawText

' ################################################################################################################################################################
' BEGIN COLOR ARRAY FUNCTIONS #COLR
' ################################################################################################################################################################

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

Sub AddColor (ColorValue As _Unsigned Long, arrColor() As _Unsigned Long)
    ReDim _Preserve arrColor(0 To UBound(arrColor) + 1) As _Unsigned Long
    arrColor(UBound(arrColor)) = ColorValue
End Sub ' AddColor

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

Sub AddColors (ColorValue As _Unsigned Long, arrColor() As _Unsigned Long, HowMany As Long)
    Dim iLoop As Integer
    For iLoop = 1 To HowMany
        AddColor ColorValue, arrColor()
    Next iLoop
End Sub ' AddColors

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

Sub AddShapeColors (arrColor() As _Unsigned Long)
    Dim iNum As Integer
    iNum = 1
    AddColors cRed, arrColor(), iNum
    AddColors cOrangeRed, arrColor(), iNum
    AddColors cDarkOrange, arrColor(), iNum
    AddColors cOrange, arrColor(), iNum
    AddColors cGold, arrColor(), iNum
    AddColors cYellow, arrColor(), iNum
    AddColors cChartreuse, arrColor(), iNum
    AddColors cOliveDrab1, arrColor(), iNum
    AddColors cLime, arrColor(), iNum
    AddColors cMediumSpringGreen, arrColor(), iNum
    AddColors cSpringGreen, arrColor(), iNum
    AddColors cCyan, arrColor(), iNum
    AddColors cDeepSkyBlue, arrColor(), iNum
    AddColors cDodgerBlue, arrColor(), iNum
    AddColors cSeaBlue, arrColor(), iNum
    AddColors cBlue, arrColor(), iNum
    AddColors cBluePurple, arrColor(), iNum
    AddColors cDeepPurple, arrColor(), iNum
    AddColors cPurple, arrColor(), iNum
    AddColors cPurpleRed, arrColor(), iNum
    AddColors cDarkRed, arrColor(), iNum
    AddColors cBrickRed, arrColor(), iNum
    AddColors cDarkGreen, arrColor(), iNum
    AddColors cGreen, arrColor(), iNum
    AddColors cOliveDrab, arrColor(), iNum
    AddColors cLightPink, arrColor(), iNum
    AddColors cHotPink, arrColor(), iNum
    AddColors cDeepPink, arrColor(), iNum
    AddColors cMagenta, arrColor(), iNum
    'AddColors cBlack, arrColor(), iNum
    AddColors cDimGray, arrColor(), iNum
    AddColors cGray, arrColor(), iNum
    AddColors cDarkGray, arrColor(), iNum
    AddColors cSilver, arrColor(), iNum
    AddColors cLightGray, arrColor(), iNum
    AddColors cGainsboro, arrColor(), iNum
    AddColors cWhiteSmoke, arrColor(), iNum
    AddColors cWhite, arrColor(), iNum
    AddColors cDarkBrown, arrColor(), iNum
    AddColors cLightBrown, arrColor(), iNum
    AddColors cKhaki, arrColor(), iNum
    'AddColors cEmpty , arrColor(), iNum
End Sub ' AddShapeColors

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

Sub AddGrayscaleColors (arrColor() As _Unsigned Long)
    Dim iNum As Integer
    iNum = 1
    AddColors cDimGray, arrColor(), iNum
    AddColors cGray, arrColor(), iNum
    AddColors cDarkGray, arrColor(), iNum
    AddColors cSilver, arrColor(), iNum
    AddColors cLightGray, arrColor(), iNum
    AddColors cGainsboro, arrColor(), iNum
    AddColors cWhiteSmoke, arrColor(), iNum
    AddColors cWhite, arrColor(), iNum '* 2
    AddColors cWhiteSmoke, arrColor(), iNum
    AddColors cGainsboro, arrColor(), iNum
    AddColors cLightGray, arrColor(), iNum
    AddColors cSilver, arrColor(), iNum
    AddColors cDarkGray, arrColor(), iNum
    AddColors cGray, arrColor(), iNum
End Sub ' AddGrayscaleColors

' ################################################################################################################################################################
' END COLOR ARRAY FUNCTIONS @COLR
' ################################################################################################################################################################

' ################################################################################################################################################################
' BEGIN COLOR UTILITY FUNCTIONS #CUTIL
' ################################################################################################################################################################
Function ColorName$ (MyColor~&)
    Dim sResult As String
    If MyColor~& = cRed Then
        sResult = "cRed"
    ElseIf MyColor~& = cOrangeRed Then
        sResult = "cOrangeRed"
    ElseIf MyColor~& = cDarkOrange Then
        sResult = "cDarkOrange"
    ElseIf MyColor~& = cOrange Then
        sResult = "cOrange"
    ElseIf MyColor~& = cGold Then
        sResult = "cGold"
    ElseIf MyColor~& = cYellow Then
        sResult = "cYellow"
    ElseIf MyColor~& = cChartreuse Then
        sResult = "cChartreuse"
    ElseIf MyColor~& = cOliveDrab1 Then
        sResult = "cOliveDrab1"
    ElseIf MyColor~& = cLime Then
        sResult = "cLime"
    ElseIf MyColor~& = cMediumSpringGreen Then
        sResult = "cMediumSpringGreen"
    ElseIf MyColor~& = cSpringGreen Then
        sResult = "cSpringGreen"
    ElseIf MyColor~& = cCyan Then
        sResult = "cCyan"
    ElseIf MyColor~& = cDeepSkyBlue Then
        sResult = "cDeepSkyBlue"
    ElseIf MyColor~& = cDodgerBlue Then
        sResult = "cDodgerBlue"
    ElseIf MyColor~& = cSeaBlue Then
        sResult = "cSeaBlue"
    ElseIf MyColor~& = cBlue Then
        sResult = "cBlue"
    ElseIf MyColor~& = cBluePurple Then
        sResult = "cBluePurple"
    ElseIf MyColor~& = cDeepPurple Then
        sResult = "cDeepPurple"
    ElseIf MyColor~& = cPurple Then
        sResult = "cPurple"
    ElseIf MyColor~& = cPurpleRed Then
        sResult = "cPurpleRed"
    ElseIf MyColor~& = cDarkRed Then
        sResult = "cDarkRed"
    ElseIf MyColor~& = cBrickRed Then
        sResult = "cBrickRed"
    ElseIf MyColor~& = cDarkGreen Then
        sResult = "cDarkGreen"
    ElseIf MyColor~& = cGreen Then
        sResult = "cGreen"
    ElseIf MyColor~& = cOliveDrab Then
        sResult = "cOliveDrab"
    ElseIf MyColor~& = cLightPink Then
        sResult = "cLightPink"
    ElseIf MyColor~& = cHotPink Then
        sResult = "cHotPink"
    ElseIf MyColor~& = cDeepPink Then
        sResult = "cDeepPink"
    ElseIf MyColor~& = cMagenta Then
        sResult = "cMagenta"
    ElseIf MyColor~& = cBlack Then
        sResult = "cBlack"
    ElseIf MyColor~& = cDimGray Then
        sResult = "cDimGray"
    ElseIf MyColor~& = cGray Then
        sResult = "cGray"
    ElseIf MyColor~& = cDarkGray Then
        sResult = "cDarkGray"
    ElseIf MyColor~& = cSilver Then
        sResult = "cSilver"
    ElseIf MyColor~& = cLightGray Then
        sResult = "cLightGray"
    ElseIf MyColor~& = cGainsboro Then
        sResult = "cGainsboro"
    ElseIf MyColor~& = cWhiteSmoke Then
        sResult = "cWhiteSmoke"
    ElseIf MyColor~& = cWhite Then
        sResult = "cWhite"
    ElseIf MyColor~& = cDarkBrown Then
        sResult = "cDarkBrown"
    ElseIf MyColor~& = cLightBrown Then
        sResult = "cLightBrown"
    ElseIf MyColor~& = cKhaki Then
        sResult = "cKhaki"
    ElseIf MyColor~& = cEmpty Then
        sResult = "cEmpty"
    Else
        'COLOR _RGB(_RED(S), _GREEN(S), _BLUE(S))
        'r1& = _Red32(p1): g1& = _Green32(p1): b1& = _Blue32(p1): a1& = _Alpha32(p1)
        sResult = "(" + _
            _Trim$(Str$(_Red32(MyColor~&))) + "," + _
            _Trim$(Str$(_Green32(MyColor~&))) + "," + _
            _Trim$(Str$(_Blue32(MyColor~&))) + "," + _
            _Trim$(Str$(_Alpha32(MyColor~&))) + _
            ")"
    End If
    ColorName$ = sResult
End Function ' ColorName$
' ################################################################################################################################################################
' END COLOR UTILITY FUNCTIONS @CUTIL
' ################################################################################################################################################################

' ################################################################################################################################################################
' 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)
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 = _RGB32(0, 0, 0, 0)
End Function ' cEmpty~&

' ################################################################################################################################################################
' END COLOR FUNCTIONS @COLOR
' ################################################################################################################################################################

' ################################################################################################################################################################
' BEGIN KEYBOARD CODES 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 CODES FUNCTIONS @KEYCODE
' ################################################################################################################################################################

' ################################################################################################################################################################
' 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$

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

$If  Then
    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$
$End If

' /////////////////////////////////////////////////////////////////////////////
' 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

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

Function cstr$ (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$

' /////////////////////////////////////////////////////////////////////////////
' 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

' /////////////////////////////////////////////////////////////////////////////
' 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

' /////////////////////////////////////////////////////////////////////////////
' Draws scaled + rotated text to screen
' by BPlus

' Scale rotate font text strings by B+
' https://qb64phoenix.com/forum/showthread.php?tid=414&highlight=rotate+text

' INPUT:
' S$ is the string to display
' c is the color (will have a transparent background)
' midX and midY is the center of where you want to display the string
' xScale would multiply 8 pixel width of default font
' yScale would multiply the 16 pixel height of the default font
' Rotation is in Radian units, use _D2R to convert Degree units to Radian units

' DEPENDENCIES:
' drwString needs sub RotoZoom2, intended for graphics screens using the default font.

Sub drwString (S$, c As _Unsigned Long, midX, midY, xScale, yScale, Rotation As Single)
    I& = _NewImage(_PrintWidth(S$), _FontHeight, 32)
    _Dest I&
    Color c, _RGBA32(0, 0, 0, 0)
    _PrintString (0, 0), S$
    _Dest storeDest&
    RotoZoom2 midX, midY, I&, xScale, yScale, Rotation
    _FreeImage I&
End Sub ' drwString

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

Sub DumpScreenAndFontSize ()
    Dim iCols As Integer
    Dim iRows As Integer
    'Screen _NewImage(1024, 768, 32): _ScreenMove 0, 0
    iCols = _Width(0) \ _FontWidth
    iRows = _Height(0) \ _FontHeight
    Print "_Width(0)  =" + _Trim$(Str$(_Width(0)))
    Print "_Height(0) =" + _Trim$(Str$(_Height(0)))
    Print "_FontWidth =" + _Trim$(Str$(_FontWidth))
    Print "_FontHeight=" + _Trim$(Str$(_FontHeight))
    Print "iCols = _Width(0)  \ _FontWidth  = " + _Trim$(Str$(iCols))
    Print "iRows = _Height(0) \ _FontHeight = " + _Trim$(Str$(iRows))
End Sub ' DumpScreenAndFontSize

' /////////////////////////////////////////////////////////////////////////////
' 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

' /////////////////////////////////////////////////////////////////////////////
' Gets the # of seconds so far today

Function GetTimeSeconds& ()
    Dim result&: result& = 0
    Dim sTime$: sTime$ = Time$
    Dim sHH24$: sHH24$ = Mid$(sTime$, 1, 2)
    Dim sMI$: sMI$ = Mid$(sTime$, 4, 2)
    Dim sSS$: sSS$ = Mid$(sTime$, 7, 2)
   
    result& = result& + Val(sSS$)
    result& = result& + (Val(sMI$) * 60)
    result& = result& + ((Val(sHH24$) * 60) * 60)
   
    ' RETURN RESULT
    GetTimeSeconds& = result&
End Function ' GetTimeSeconds&

' /////////////////////////////////////////////////////////////////////////////
' 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

' /////////////////////////////////////////////////////////////////////////////
' iNum% = PromptForIntegerInRange%("Please type a number between {min} and {max} (or blank to quit).", 1, 4, 0) ' prompt, min, max, default

Function PromptForIntegerInRange% (sPrompt$, iMin%, iMax%, iDefault%)
    Dim iValue%
    Dim bFinished%
    Dim sPrompt1$
    Dim in$

    If Len(sPrompt$) > 0 Then
        sPrompt1$ = sPrompt$
    Else
        sPrompt1$ = "Please type a number between {min} and {max} (or blank to quit)."
    End If

    sPrompt1$ = Replace$(sPrompt1$, "{min}", cstr$(iMin%))
    sPrompt1$ = Replace$(sPrompt1$, "{max}", cstr$(iMax%))

    bFinished% = FALSE
    Do
        Print sPrompt1$

        Input in$
        in$ = _Trim$(in$)
        If Len(in$) > 0 Then
            If IsNumber(in$) Then
                iValue% = Val(in$)
                If iValue% >= iMin% And iValue% <= iMax% Then
                    'bFinished% = TRUE
                    Exit Do
                Else
                    Print "Number out of range."
                    Print
                End If
            Else
                Print "Not a valid number."
                Print
            End If
        Else
            iValue% = iDefault%
            Exit Do
            'bFinished% = TRUE
        End If
    Loop Until bFinished% = TRUE

    PromptForIntegerInRange% = iValue%
End Function ' PromptForIntegerInRange%

' /////////////////////////////////////////////////////////////////////////////
' 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

' /////////////////////////////////////////////////////////////////////////////
' Initializes random number generator seed

' NOTE:
' using # of seconds so far in the day
' may be slightly more random than Randomize Timer
' unless user runs program at exact same time every day

Sub InitializeRandom
    Dim iSeed As Integer
   
    'iSeed = GetTimeSeconds& MOD 32767
   
    t9# = (Timer * 1000000) Mod 32767
   
    Randomize iSeed
    'print "Randomize " + cstr$(iSeed)
    'Sleep
End Sub ' InitializeRandom

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

' Note: random-number generator should be initialized with
'       InitializeRandom or Randomize Timer

Function RandomNumber% (Min%, Max%)
    Dim NumSpread%
    NumSpread% = (Max% - Min%) + 1
    RandomNumber% = Int(Rnd * NumSpread%) + Min% ' GET RANDOM # BETWEEN Max% AND Min%
End Function ' RandomNumber%

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

$If  Then
    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
$End If

' /////////////////////////////////////////////////////////////////////////////
' 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$

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

$If  Then
    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
$End If

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

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

' /////////////////////////////////////////////////////////////////////////////
' This sub gives really nice control over displaying an Image.
' by BPlus

' Scale rotate font text strings by B+
' https://qb64phoenix.com/forum/showthread.php?tid=414&highlight=rotate+text

' USED BY: drwString

Sub RotoZoom2 (centerX As Long, centerY As Long, Image As Long, xScale As Single, yScale As Single, Rotation As Single)
    Dim px(3) As Single: Dim py(3) As Single
    W& = _Width(Image&): H& = _Height(Image&)
    px(0) = -W& / 2: py(0) = -H& / 2: px(1) = -W& / 2: py(1) = H& / 2
    px(2) = W& / 2: py(2) = H& / 2: px(3) = W& / 2: py(3) = -H& / 2
    sinr! = Sin(-Rotation): cosr! = Cos(-Rotation)
    For i& = 0 To 3
        x2& = (px(i&) * cosr! + sinr! * py(i&)) * xScale + centerX: y2& = (py(i&) * cosr! - px(i&) * sinr!) * yScale + centerY
        px(i&) = x2&: py(i&) = y2&
    Next
    _MapTriangle (0, 0)-(0, H& - 1)-(W& - 1, H& - 1), Image& To(px(0), py(0))-(px(1), py(1))-(px(2), py(2))
    _MapTriangle (0, 0)-(W& - 1, 0)-(W& - 1, H& - 1), Image& To(px(0), py(0))-(px(3), py(3))-(px(2), py(2))
End Sub ' RotoZoom2

'' /////////////////////////////////////////////////////////////////////////////
'' https://qb64phoenix.com/forum/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%

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

$If  Then
    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
$End If

' /////////////////////////////////////////////////////////////////////////////
' 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

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

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

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

$If  Then
    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
$End If

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

$If  Then
    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
$End If

' /////////////////////////////////////////////////////////////////////////////
' 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 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

' same as DebugPrint but includes a timestamp
Sub DebugPrintTS (MyString As String)
    If m_bDebug = TRUE Then
        '_Echo MyString

        ReDim arrLines(-1) As String
        Dim iLoop As Integer
        Dim sTimeStamp As String
       
        sTimeStamp = GetCurrentDateTime$("{mm}/{dd}/{yyyy} {hh}:{nn}:{ss} {ampm}")
       
        split MyString, Chr$(13), arrLines()
        For iLoop = LBound(arrLines) To UBound(arrLines)
            _Echo sTimeStamp + " " + arrLines(iLoop)
        Next iLoop
    End If
End Sub ' DebugPrintTS

'' /////////////////////////////////////////////////////////////////////////////
'
'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%

' Screen _NewImage(1024, 768, 32): _ScreenMove 0, 0
' iCols = _Width(0) \ _FontWidth
' iRows = _Height(0) \ _FontHeight
' Print "_Width(0)  =" + _Trim$(Str$(_Width(0)))
' Print "_Height(0) =" + _Trim$(Str$(_Height(0)))
' Print "_FontWidth =" + _Trim$(Str$(_FontWidth))
' Print "_FontHeight=" + _Trim$(Str$(_FontHeight))
' Print "iCols = _Width(0)  \ _FontWidth  = " + _Trim$(Str$(iCols))
' Print "iRows = _Height(0) \ _FontHeight = " + _Trim$(Str$(iRows))

$If  Then
    'Pete:
    'Oh, does anyone else like how using $IF/THEN works as a block REM statement?
    'I mean I'd rather we had a QB64 block remark statement like the one used for JavaScript, but thi hack will do.
$End If

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

'#END
Simple polygon test/demo "polygon33.bas":

Code: (Select All)
Dim Shared m_sTitle As String: m_sTitle = "2D Vector Shapes v0.33 by madscijr"
_Title m_sTitle ' display in the Window's title bar

' Simple test of vector graphics,
' borrowed graphics objects and format from Widescreen Asteroids by Terry Ritchie.

' DONE
' * We now can draw opaque shapes (still need to get working for shapes with non-contiguous areas)
' * Display looks prettier!
'   - Shapes not active are drawn in an animated dashes
'   - Text display is color coded to shapes
' * Tried adding twinkling stars (hard to see, need to fix)
' * fix twinkling stars

' TO DO:
' * get fill working for shapes with non-contiguous areas)
'   - define "fill coordinates" property or array
'   - auto find these in shape
'   - eliminate duplicates
'   - store the remaining values store in shape data
'   - use for PAINT when drawing fill color

' BOOLEAN CONSTANTS
Const FALSE = 0
Const TRUE = Not FALSE

' OTHER SETTINGS
Const cFPS = 120
Const cMinStars = 50
Const cMaxStars = 150
Const cSpeed = 2

' 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
    z As Integer ' zorder in which object is stacked (in front of or behind other objects)
    FillColor As _Unsigned Long ' color to fill in object with (use cEmpty for transparent)
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 LOCATION + COLOR OF EACH STAR
Type StarType
    x As Integer
    y As Integer
   
    ColorIndex As _Unsigned Long ' the star's current color
    TwinkleCounter As Integer ' counter for twinkles
    MaxTwinkCount As Integer ' controls how fast the star twinkles
   
    width As Integer
    MinWidth As Integer ' smallest width
    MaxWidth As Integer ' largest width
    WidthCounter As Integer ' counter for width
    MaxWidthCount As Integer ' controls how fast the star size fluctuates
    BigCounter As Integer ' counter for max width
    MaxBigCount As Integer ' controls how long the star stays big
End Type ' StarType

' OBJECT VARIABLES
ReDim Shared m_arrObject(1 To 6) As ObjectType
ReDim Shared m_arrOrder(1 To 6) As Integer
ReDim Shared m_arrLines(1 To 8, 1 To 32) As CoordType
ReDim Shared m_arrColor(1 To 6) As _Unsigned Long
ReDim Shared m_arrLineStyle(1 To 8) As Long
ReDim Shared m_arrStars(1 To cMaxStars) As StarType
ReDim Shared m_arrGrayColor(-1) As _Unsigned Long

' =============================================================================
' START THE MAIN ROUTINE
DrawVectorObjectTest1

' =============================================================================
' FINISH
Screen 0
System ' return control to the operating system

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

Sub DrawVectorObjectTest1
    ' LOCAL VARIABLES
    Dim iFPS As Integer: iFPS = cFPS
    Dim iLoop As Integer
    Dim iObject As Integer
    Dim iLayer As Integer
    Dim iLine As Integer
    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 iStyleCountMax As Integer: iStyleCountMax = iFPS \ 12 ' change 4x a second
    Dim iStyleCountNext As Integer: iStyleCountNext = 0
    Dim iLineStyleIndex As Integer
    Dim lngLineStyle ' line style
    Dim lngDashedLineStyle ' line style for other objects
    Dim lngSolidLineStyle ' selected object's line style
    Dim iNumStars As Integer
    Dim iValue As Integer
    Dim iMinValue As Integer
    Dim iMaxValue As Integer
    Dim iStarLoop As Integer
    Dim x1%
    Dim x2%
    Dim y1%
    Dim y2%
    Dim imgStars& ' used for drawing background
    Dim imgText& ' used for drawing text
    Dim imgObjects& ' used for drawing objects
    Dim imgTemp& ' temporary drawing area
   
    ' =============================================================================
    ' INITIALIZE
    InitializeRandom
    Screen _NewImage(iMaxX, iMaxY, 32) ' 100 text columns x 40 text rows
   
    ' USE LATER FOR DRAWING LAYERS:
    imgStars& = _NewImage(iMaxX, iMaxY, 32) ' background stars
    imgText& = _NewImage(iMaxX, iMaxY, 32) ' text
    imgObjects& = _NewImage(iMaxX, iMaxY, 32) ' frontground objects
    imgTemp& = _NewImage(iMaxX, iMaxY, 32) ' temporary
   
    ' =============================================================================
    ' START NEW GAME
    Do
        _KeyClear

        ' CONFIGURE PRINTING FOR _PrintString
        _PrintMode _FillBackground
        '_PrintMode _KEEPBACKGROUND

        ' INIT OBJECT DEFINITIONS
        InitVectorObjects
       
        ' INIT VARS
        sKey = ""
       
        ' PLACE OBJECTS
        iX = 0: iY = 0
        iValue = UBound(m_arrObject)
        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
            m_arrObject(iObject).z = iValue
            m_arrObject(iObject).FillColor = cBlack
           
            iX = iX + 200
            If iX > 800 Then
                iX = 0
                iY = iY + 200
                If iY > 640 Then
                    iY = 0
                End If
            End If
            iValue = iValue - 1
        Next iObject
       
        ' INIT OBJECT Z-ORDER ARRAY
        ReDim m_arrOrder(LBound(m_arrObject) To UBound(m_arrObject)) As Integer
        For iObject = LBound(m_arrObject) To UBound(m_arrObject)
            m_arrOrder(m_arrObject(iObject).z) = iObject
        Next iObject
       
        ' SAVE LINE STYLE SEQUENCE
        iLoop = 0
        iLoop = iLoop + 1: m_arrLineStyle(iLoop) = 61680 ' 1111000011110000
        iLoop = iLoop + 1: m_arrLineStyle(iLoop) = 30840 ' 0111100001111000
        iLoop = iLoop + 1: m_arrLineStyle(iLoop) = 15420 ' 0011110000111100
        iLoop = iLoop + 1: m_arrLineStyle(iLoop) = 7710 ' 0001111000011110
        iLoop = iLoop + 1: m_arrLineStyle(iLoop) = 3855 ' 0000111100001111
        iLoop = iLoop + 1: m_arrLineStyle(iLoop) = 34695 ' 1000011110000111
        iLoop = iLoop + 1: m_arrLineStyle(iLoop) = 50115 ' 1100001111000011
        iLoop = iLoop + 1: m_arrLineStyle(iLoop) = 57825 ' 1110000111100001
        iLineStyleIndex = LBound(m_arrLineStyle)
        lngDashedLineStyle = m_arrLineStyle(iLineStyleIndex)
        lngSolidLineStyle = 65535

        ' SAVE COLORS FOR EACH OBJECT (FROM FIRST LINE SEGMENT) TO ARRAY FOR TEXT
        For iObject = LBound(m_arrObject) To UBound(m_arrObject)
            iLine = LBound(m_arrLines, 2)
            m_arrColor(iObject) = m_arrLines(iObject, iLine).color
        Next iObject
       
        ' SAVE COLORS FOR STARS AND GRAYSCALE OBJECTS
        AddGrayscaleColors m_arrGrayColor()
       
        ' -----------------------------------------------------------------------------
        ' PLACE STARS RANDOMLY
        iNumStars = RandomNumber%(cMinStars, cMaxStars)
        ReDim m_arrStars(1 To iNumStars) As StarType
        For iLoop = 1 To iNumStars
            m_arrStars(iLoop).x = RandomNumber%(iMinX, iMaxX)
            m_arrStars(iLoop).y = RandomNumber%(iMinY, iMaxY)
            m_arrStars(iLoop).ColorIndex = RandomNumber%(LBound(m_arrGrayColor), UBound(m_arrGrayColor))
           
            ' Assign a width 1-3 (with different probability for each)
            iValue = RandomNumber%(1, 100)
            If iValue > 98 Then
                m_arrStars(iLoop).MinWidth = RandomNumber%(2, 3)
                m_arrStars(iLoop).MaxWidth = 3
            ElseIf iValue > 85 Then
                m_arrStars(iLoop).MinWidth = RandomNumber%(1, 2)
                m_arrStars(iLoop).MaxWidth = 2
            Else
                m_arrStars(iLoop).MinWidth = RandomNumber%(0, 1)
                m_arrStars(iLoop).MaxWidth = 1
            End If
            ' Set initial width to normal (MaxWidth)
            m_arrStars(iLoop).width = m_arrStars(iLoop).MaxWidth
           
            ' Determine how quickly size changes
            ' Anywhere between 1/30 second and 1 seconds
            iMinValue = iFPS \ 30
            iMaxValue = iFPS
            m_arrStars(iLoop).MaxWidthCount = RandomNumber%(iMinValue, iMaxValue)
            m_arrStars(iLoop).WidthCounter = 0
           
            ' Determine how long size is changed
            ' Anywhere between 1/100 second and 1/50 seconds
            iMinValue = iFPS \ 100
            iMaxValue = iFPS \ 50
            m_arrStars(iLoop).MaxBigCount = RandomNumber%(iMinValue, iMaxValue)
            m_arrStars(iLoop).BigCounter = 0
           
            ' Determine how quickly they twinkle
            ' Anywhere between 1/120 second and 1/20 seconds
            iMinValue = iFPS \ 120
            iMaxValue = iFPS \ 20
            m_arrStars(iLoop).MaxTwinkCount = RandomNumber%(iMinValue, iMaxValue)
            m_arrStars(iLoop).TwinkleCounter = 0
        Next iLoop
       
        ' ================================================================================================================================================================
        ' BEGIN MAIN LOOP
        While TRUE = TRUE
           
            ' CLEAR OBJECTS LAYER
            _Dest imgObjects&: Cls , cEmpty
           
            ' MOVE AND ADD ENABLED OBJECTS (IN STACKING ORDER)
            For iLayer = UBound(m_arrOrder) To LBound(m_arrOrder) Step -1
                ' Get next object
                iObject = m_arrOrder(iLayer)
               
                ' 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 - cSpeed
                            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 + cSpeed
                            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 - cSpeed
                            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 + cSpeed
                            If m_arrObject(iObject).y > iMaxY Then
                                m_arrObject(iObject).y = iMinY
                            End If
                        End If
                    End If
                   
                    ' Clear temporary (layer and draw on it
                    _Dest imgTemp&: Cls , cEmpty
                   
                    ' Draw object's line segments
                    For iLine = LBound(m_arrLines, 2) To UBound(m_arrLines, 2)
                        If m_arrLines(iObject, iLine).IsLast = FALSE Then
                            'Line (x1%, y1%)-(x2%, y2%), arrColor(arrStars(iLoop).ColorIndex), BF, lngLineStyle
                            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, , lngSolidLineStyle
                        Else
                            Exit For
                        End If
                    Next iLine
                   
                    ' Draw fill color if not transparent
                    If m_arrObject(iObject).FillColor <> cEmpty Then
                        ' Fill in current object with its fill color...
                        'PAINT [STEP] (column%, row%), fillColor[, borderColor%]
                        Paint (m_arrObject(iObject).x, m_arrObject(iObject).y), _
                            m_arrObject(iObject).FillColor, m_arrColor(iObject)
                    End If
                   
                    ' Make other objects appear drawn with a dashed line
                    If iObject <> iWhich Then
                        ' Outline with a dashed line
                        For iLine = LBound(m_arrLines, 2) To UBound(m_arrLines, 2)
                            If m_arrLines(iObject, iLine).IsLast = FALSE Then
                                'Line (x1%, y1%)-(x2%, y2%), arrColor(arrStars(iLoop).ColorIndex), BF, lngLineStyle
                                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) _
                                    , _
                                    cBlack, , lngDashedLineStyle
                            Else
                                Exit For
                            End If
                        Next iLine
                    End If
                   
                    ' Add new object to objects layer
                    '_PUTIMAGE , sourceHandle&, destHandle& 'size full source to fit full destination area
                    _PutImage , imgTemp&, imgObjects&
                   
                End If
            Next iLayer
           
            ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
            ' BEGIN STARS
           
            ' Twinkle twinkle little stars
            _Dest imgStars&: Cls , cEmpty
            For iStarLoop = LBound(m_arrStars) To UBound(m_arrStars)
                ' increment twinkle counter
                m_arrStars(iStarLoop).TwinkleCounter = m_arrStars(iStarLoop).TwinkleCounter + 1
               
                ' is it time to twinkle the color?
                If m_arrStars(iStarLoop).TwinkleCounter > m_arrStars(iStarLoop).MaxTwinkCount Then
                    m_arrStars(iStarLoop).TwinkleCounter = 0 ' reset counter
                    m_arrStars(iStarLoop).ColorIndex = m_arrStars(iStarLoop).ColorIndex + 1 ' increment color
                    If m_arrStars(iStarLoop).ColorIndex > UBound(m_arrGrayColor) Then
                        m_arrStars(iStarLoop).ColorIndex = LBound(m_arrGrayColor) ' reset color
                    End If
                End If
               
                ' increment width counter
                If m_arrStars(iStarLoop).BigCounter = 0 Then
                    m_arrStars(iStarLoop).WidthCounter = m_arrStars(iStarLoop).WidthCounter + 1
                   
                    ' is it time to fluctuate the width
                    If m_arrStars(iStarLoop).WidthCounter > m_arrStars(iStarLoop).MaxWidthCount Then
                        m_arrStars(iStarLoop).WidthCounter = 0 ' reset counter
                        m_arrStars(iStarLoop).BigCounter = 1 ' start big counter
                        m_arrStars(iStarLoop).width = m_arrStars(iStarLoop).MinWidth ' twinkle width
                    Else
                        m_arrStars(iStarLoop).width = m_arrStars(iStarLoop).MaxWidth ' normal width
                    End If
                Else
                    ' increment big counter
                    m_arrStars(iStarLoop).BigCounter = m_arrStars(iStarLoop).BigCounter + 1
                   
                    ' is it time to return to normal size?
                    If m_arrStars(iStarLoop).BigCounter > m_arrStars(iStarLoop).MaxBigCount Then
                        m_arrStars(iStarLoop).BigCounter = 0 ' reset counter
                        m_arrStars(iStarLoop).width = m_arrStars(iStarLoop).MaxWidth ' normal width
                    End If
                End If
               
                ' get size
                x1% = m_arrStars(iStarLoop).x: x2% = x1% + m_arrStars(iStarLoop).width
                y1% = m_arrStars(iStarLoop).y: y2% = y1% + m_arrStars(iStarLoop).width
               
                ' (re)draw it
                Line (x1%, y1%)-(x2%, y2%), m_arrGrayColor(m_arrStars(iStarLoop).ColorIndex), BF
            Next iStarLoop
            ' END STARS
            ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
           
            ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
            ' SHOW TEXT
            _Dest imgText&: Cls , cEmpty
            DrawText sKey, iWhich
            ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
           
            ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
            ' BEGIN UPDATE SCREEN
           
            ' COPY LAYERS TO SCREEN
            _Dest 0: Cls , cBlack
            If imgStars& < -1 Then
                _PutImage , imgStars&, 0
            End If
            If imgText& < -1 Then
                _PutImage , imgText&, 0
            End If
            If imgObjects& < -1 Then
                _PutImage , imgObjects&, 0
            End If
           
            ' UPDATE THE SCREEN
            _Display
           
            ' END UPDATE SCREEN
            ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
           
            ' 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

            ' CYCLE LINE STYLE FOR ACTIVE OBJECT
            iStyleCountNext = iStyleCountNext + 1 ' increment line style counter
            If iStyleCountNext > iStyleCountMax Then
                iStyleCountNext = 0
                iLineStyleIndex = iLineStyleIndex + 1
                If iLineStyleIndex > UBound(m_arrLineStyle) Then
                    iLineStyleIndex = LBound(m_arrLineStyle)
                End If
                lngDashedLineStyle = m_arrLineStyle(iLineStyleIndex)
            End If

            ' CLEAR KEYBOARD BUFFER
            _KeyClear

            ' CONTROL GAME SPEED
            _Limit iFPS
        Wend
        ' END MAIN LOOP
        ' ================================================================================================================================================================

        ' 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
   
    ' CLEAR IMAGES
    Screen 0
    If imgStars& < -1 Then _FreeImage imgStars&
    If imgText& < -1 Then _FreeImage imgText&
    If imgObjects& < -1 Then _FreeImage imgObjects&
    If imgTemp& < -1 Then _FreeImage imgTemp&
   
    ' RETURN TO AUTODISPLAY
    _AutoDisplay

End Sub ' DrawVectorObjectTest1

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

' Receives:
' Long iInput1 = value (0-65535) to conver to binary

' Returns a 16 character string of "1" and "0"
' (a 16-bit binary representation of iInput1)

Function BinaryStringFromLong$ (iInput1 As Long)
    Dim sBinary As String
    Dim iInput As Long
    Dim iLoop As Integer
    Dim iNextValue As Long

    sBinary = ""
    iInput = iInput1
    If iInput >= 0 Then
        For iLoop = 15 To 0 Step -1
            iNextValue = 2 ^ iLoop
            If (iInput \ iNextValue) > 0 Then
                sBinary = sBinary + "1"
            Else
                sBinary = sBinary + "0"
            End If
            iInput = iInput Mod iNextValue
        Next iLoop
    End If

    BinaryStringFromLong$ = sBinary
End Function ' BinaryStringFromLong$

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

' Receives:
' String sBitPattern = 16 character string of "1" and "0"
'                      (a 16-bit binary representation)

' Returns the bit pattern converted to a long integer.

Function LongFromBinaryString& (sBitPattern As String)
    Dim sInput As String: sInput = sBitPattern
    Dim iLoop As Integer
    Dim MyLong As Long

    MyLong = 0

    If Len(sInput) >= 16 Then
        For iLoop = 0 To 15
            If Mid$(sInput, 16 - iLoop, 1) = "1" Then
                MyLong = MyLong + (2 ^ iLoop)
            End If
        Next iLoop
    End If

    LongFromBinaryString& = MyLong
End Function ' LongFromBinaryString&

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

' future versions will pull this data from an editable file

Sub 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
            m_arrLines(iObject, iLine).IsLast = TRUE
            m_arrObject(iObject).IsEnabled = TRUE
            Exit For
        ElseIf b1 = -254 Then
            m_arrLines(iObject, iLine).IsLast = TRUE
            m_arrObject(iObject).IsEnabled = TRUE
            iObject = iObject + 1
            iLine = 1
            If iObject > UBound(m_arrLines, 1) Then Exit For
        Else
            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
            If iLine > UBound(m_arrLines, 2) Then Exit For
        End If
    Next iLoop%

    VectorData:
    '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 = yellow
    Data 9,-62,60,-21,255,255,0
    Data 60,-21,62,-3,255,255,0
    Data 62,-3,24,13,255,255,0
    Data 24,13,53,34,255,255,0
    Data 53,34,38,55,255,255,0
    Data 38,55,20,40,255,255,0
    Data 20,40,-37,61,255,255,0
    Data -37,61,-63,15,255,255,0
    Data -63,15,-57,-24,255,255,0
    Data -57,-24,-24,-24,255,255,0
    Data -24,-24,-38,-45,255,255,0
    Data -38,-45,9,-62,255,255,0
    Data 0,0,0,0,-254,-254,-254

    'objmouse = white
    Data 0,-10,6,3,255,255,255
    Data 6,3,1,2,255,255,255
    Data 1,2,1,10,255,255,255
    Data 1,10,-1,10,255,255,255
    Data -1,10,-1,2,255,255,255
    Data -1,2,-6,3,255,255,255
    Data -6,3,0,-10,255,255,255
    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
    'Data 0,18,0,18,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

' /////////////////////////////////////////////////////////////////////////////
' SHOW INSTRUMENTS + INSTRUCTIONS
' 800x600 = 40 rows x 100 columns

sub DrawText( _
    sKey as string, _
    iWhich as integer _
    )

    Dim iObject As Integer
    Dim RowNum As Integer
    Dim sFlag As String
    Dim iNum As Integer
   
    RowNum = 0

    Color cWhite, cEmpty
    RowNum = RowNum + 1: PrintAt RowNum, 1, m_sTitle
    RowNum = RowNum + 1
   
    Color cYellow, cEmpty
    RowNum = RowNum + 1: PrintAt RowNum, 1, "----------------------------------------"
    RowNum = RowNum + 1: PrintAt RowNum, 1, "Press 1-6 to select active object."
    RowNum = RowNum + 1: PrintAt RowNum, 1, "Arrow keys move active object."
    RowNum = RowNum + 1: PrintAt RowNum, 1, "Press ESC to quit"
    RowNum = RowNum + 1: PrintAt RowNum, 1, "----------------------------------------"
    RowNum = RowNum + 1

    ' SHOW OBJECTS
    Color cGray, cEmpty
    RowNum = RowNum + 1: PrintAt RowNum, 1, "----------------------------------------"

    iNum = 0
    For iObject = LBound(m_arrObject) To UBound(m_arrObject)
        iNum = iNum + 1
        If m_arrObject(iObject).IsEnabled = TRUE Then
            Color m_arrColor(iObject), cEmpty
            If iObject = iWhich Then
                'Color cCyan, cEmpty
                sFlag = "-> "
            Else
                'Color cDodgerBlue, cEmpty
                sFlag = "   "
            End If
        Else
            Color cGray, cEmpty
            sFlag = "   "
        End If

        RowNum = RowNum + 1: PrintAt RowNum, 1, "" + _
            sFlag + _
            cstr$(iNum) + ". " + _
            "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) + ")" + _
            ""
    Next iObject

    Color cGray, cEmpty
    RowNum = RowNum + 1: PrintAt RowNum, 1, "----------------------------------------"

    RowNum = RowNum + 2

    '' SHOW ACTIVE OBJECT
    'Color cWhite
    'RowNum = RowNum + 1: PrintAt RowNum, 1, "Object #   : " + cstr$(iWhich)

    ' SHOW INPUT
    Color cLime, cEmpty
    RowNum = RowNum + 1: PrintAt RowNum, 1, "Controls   : " + RightPadString$(sKey, 10, " ") + "   "
   
End Sub ' DrawText

' ################################################################################################################################################################
' BEGIN COLOR ARRAY FUNCTIONS #COLR
' ################################################################################################################################################################

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

Sub AddColor (ColorValue As _Unsigned Long, arrColor() As _Unsigned Long)
    ReDim _Preserve arrColor(0 To UBound(arrColor) + 1) As _Unsigned Long
    arrColor(UBound(arrColor)) = ColorValue
End Sub ' AddColor

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

Sub AddColors (ColorValue As _Unsigned Long, arrColor() As _Unsigned Long, HowMany As Long)
    Dim iLoop As Integer
    For iLoop = 1 To HowMany
        AddColor ColorValue, arrColor()
    Next iLoop
End Sub ' AddColors

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

Sub AddGrayscaleColors (arrColor() As _Unsigned Long)
    Dim iNum As Integer
    iNum = 1
    AddColors cDimGray, arrColor(), iNum
    AddColors cGray, arrColor(), iNum
    AddColors cDarkGray, arrColor(), iNum
    AddColors cSilver, arrColor(), iNum
    AddColors cLightGray, arrColor(), iNum
    AddColors cGainsboro, arrColor(), iNum
    AddColors cWhiteSmoke, arrColor(), iNum
    AddColors cWhite, arrColor(), iNum '* 2
    AddColors cWhiteSmoke, arrColor(), iNum
    AddColors cGainsboro, arrColor(), iNum
    AddColors cLightGray, arrColor(), iNum
    AddColors cSilver, arrColor(), iNum
    AddColors cDarkGray, arrColor(), iNum
    AddColors cGray, arrColor(), iNum
End Sub ' AddGrayscaleColors

' ################################################################################################################################################################
' END COLOR ARRAY FUNCTIONS @COLR
' ################################################################################################################################################################

' ################################################################################################################################################################
' 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)
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 = _RGB32(0, 0, 0, 0)
End Function ' cEmpty~&

' ################################################################################################################################################################
' END COLOR FUNCTIONS @COLOR
' ################################################################################################################################################################

' ################################################################################################################################################################
' BEGIN KEYBOARD CODES 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 CODES FUNCTIONS @KEYCODE
' ################################################################################################################################################################

' ################################################################################################################################################################
' 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$

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

$If  Then
    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$
$End If

' /////////////////////////////////////////////////////////////////////////////
' 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

' /////////////////////////////////////////////////////////////////////////////
' 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$

' /////////////////////////////////////////////////////////////////////////////
' 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

' /////////////////////////////////////////////////////////////////////////////
' 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

' /////////////////////////////////////////////////////////////////////////////
' Draws scaled + rotated text to screen
' by BPlus

' Scale rotate font text strings by B+
' https://qb64phoenix.com/forum/showthread.php?tid=414&highlight=rotate+text

' INPUT:
' S$ is the string to display
' c is the color (will have a transparent background)
' midX and midY is the center of where you want to display the string
' xScale would multiply 8 pixel width of default font
' yScale would multiply the 16 pixel height of the default font
' Rotation is in Radian units, use _D2R to convert Degree units to Radian units

' DEPENDENCIES:
' drwString needs sub RotoZoom2, intended for graphics screens using the default font.

Sub drwString (S$, c As _Unsigned Long, midX, midY, xScale, yScale, Rotation As Single)
    I& = _NewImage(_PrintWidth(S$), _FontHeight, 32)
    _Dest I&
    Color c, _RGBA32(0, 0, 0, 0)
    _PrintString (0, 0), S$
    _Dest storeDest&
    RotoZoom2 midX, midY, I&, xScale, yScale, Rotation
    _FreeImage I&
End Sub ' drwString

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

Sub DumpScreenAndFontSize ()
    Dim iCols As Integer
    Dim iRows As Integer
    'Screen _NewImage(1024, 768, 32): _ScreenMove 0, 0
    iCols = _Width(0) \ _FontWidth
    iRows = _Height(0) \ _FontHeight
    Print "_Width(0)  =" + _Trim$(Str$(_Width(0)))
    Print "_Height(0) =" + _Trim$(Str$(_Height(0)))
    Print "_FontWidth =" + _Trim$(Str$(_FontWidth))
    Print "_FontHeight=" + _Trim$(Str$(_FontHeight))
    Print "iCols = _Width(0)  \ _FontWidth  = " + _Trim$(Str$(iCols))
    Print "iRows = _Height(0) \ _FontHeight = " + _Trim$(Str$(iRows))
End Sub ' DumpScreenAndFontSize

' /////////////////////////////////////////////////////////////////////////////
' 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

' /////////////////////////////////////////////////////////////////////////////
' Gets the # of seconds so far today

Function GetTimeSeconds& ()
    Dim result&: result& = 0
    Dim sTime$: sTime$ = Time$
    Dim sHH24$: sHH24$ = Mid$(sTime$, 1, 2)
    Dim sMI$: sMI$ = Mid$(sTime$, 4, 2)
    Dim sSS$: sSS$ = Mid$(sTime$, 7, 2)
   
    result& = result& + VAL(sSS$)
    result& = result& + (VAL(sMI$) * 60)
    result& = result& + ( (VAL(sHH24$) * 60) * 60)
   
    ' RETURN RESULT
    GetTimeSeconds& = result&
End Function ' GetTimeSeconds&

' /////////////////////////////////////////////////////////////////////////////
' 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

' /////////////////////////////////////////////////////////////////////////////
' iNum% = PromptForIntegerInRange%("Please type a number between {min} and {max} (or blank to quit).", 1, 4, 0) ' prompt, min, max, default

Function PromptForIntegerInRange% (sPrompt$, iMin%, iMax%, iDefault%)
    Dim iValue%
    Dim bFinished%
    Dim sPrompt1$
    Dim in$

    If Len(sPrompt$) > 0 Then
        sPrompt1$ = sPrompt$
    Else
        sPrompt1$ = "Please type a number between {min} and {max} (or blank to quit)."
    End If

    sPrompt1$ = Replace$(sPrompt1$, "{min}", cstr$(iMin%))
    sPrompt1$ = Replace$(sPrompt1$, "{max}", cstr$(iMax%))

    bFinished% = FALSE
    Do
        Print sPrompt1$

        Input in$
        in$ = _Trim$(in$)
        If Len(in$) > 0 Then
            If IsNumber(in$) Then
                iValue% = Val(in$)
                If iValue% >= iMin% And iValue% <= iMax% Then
                    'bFinished% = TRUE
                    Exit Do
                Else
                    Print "Number out of range."
                    Print
                End If
            Else
                Print "Not a valid number."
                Print
            End If
        Else
            iValue% = iDefault%
            Exit Do
            'bFinished% = TRUE
        End If
    Loop Until bFinished% = TRUE

    PromptForIntegerInRange% = iValue%
End Function ' PromptForIntegerInRange%

' /////////////////////////////////////////////////////////////////////////////
' 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

' /////////////////////////////////////////////////////////////////////////////
' Initializes random number generator seed

' NOTE:
' using # of seconds so far in the day
' may be slightly more random than Randomize Timer
' unless user runs program at exact same time every day

Sub InitializeRandom
    dim iSeed As Integer
   
    'iSeed = GetTimeSeconds& MOD 32767
   
    t9# = (Timer * 1000000) Mod 32767
   
    Randomize iSeed
    'print "Randomize " + cstr$(iSeed)
    'Sleep
End Sub ' InitializeRandom

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

' Note: random-number generator should be initialized with
'       InitializeRandom or Randomize Timer

Function RandomNumber% (Min%, Max%)
    Dim NumSpread%
    NumSpread% = (Max% - Min%) + 1
    RandomNumber% = Int(Rnd * NumSpread%) + Min% ' GET RANDOM # BETWEEN Max% AND Min%
End Function ' RandomNumber%

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

$If  Then
    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
$End If

' /////////////////////////////////////////////////////////////////////////////
' 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$

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

$If  Then
    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
$End If

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

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

' /////////////////////////////////////////////////////////////////////////////
' This sub gives really nice control over displaying an Image.
' by BPlus

' Scale rotate font text strings by B+
' https://qb64phoenix.com/forum/showthread.php?tid=414&highlight=rotate+text

' USED BY: drwString

Sub RotoZoom2 (centerX As Long, centerY As Long, Image As Long, xScale As Single, yScale As Single, Rotation As Single)
    Dim px(3) As Single: Dim py(3) As Single
    W& = _Width(Image&): H& = _Height(Image&)
    px(0) = -W& / 2: py(0) = -H& / 2: px(1) = -W& / 2: py(1) = H& / 2
    px(2) = W& / 2: py(2) = H& / 2: px(3) = W& / 2: py(3) = -H& / 2
    sinr! = Sin(-Rotation): cosr! = Cos(-Rotation)
    For i& = 0 To 3
        x2& = (px(i&) * cosr! + sinr! * py(i&)) * xScale + centerX: y2& = (py(i&) * cosr! - px(i&) * sinr!) * yScale + centerY
        px(i&) = x2&: py(i&) = y2&
    Next
    _MapTriangle (0, 0)-(0, H& - 1)-(W& - 1, H& - 1), Image& To(px(0), py(0))-(px(1), py(1))-(px(2), py(2))
    _MapTriangle (0, 0)-(W& - 1, 0)-(W& - 1, H& - 1), Image& To(px(0), py(0))-(px(3), py(3))-(px(2), py(2))
End Sub ' RotoZoom2

'' /////////////////////////////////////////////////////////////////////////////
'' https://qb64phoenix.com/forum/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%

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

$If  Then
    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
$End If

' /////////////////////////////////////////////////////////////////////////////
' 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

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

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

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

$If  Then
    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
$End If

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

$If  Then
    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
$End If

' /////////////////////////////////////////////////////////////////////////////
' 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 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%

' Screen _NewImage(1024, 768, 32): _ScreenMove 0, 0
' iCols = _Width(0) \ _FontWidth
' iRows = _Height(0) \ _FontHeight
' Print "_Width(0)  =" + _Trim$(Str$(_Width(0)))
' Print "_Height(0) =" + _Trim$(Str$(_Height(0)))
' Print "_FontWidth =" + _Trim$(Str$(_FontWidth))
' Print "_FontHeight=" + _Trim$(Str$(_FontHeight))
' Print "iCols = _Width(0)  \ _FontWidth  = " + _Trim$(Str$(iCols))
' Print "iRows = _Height(0) \ _FontHeight = " + _Trim$(Str$(iRows))

$If  Then
    'Pete:
    'Oh, does anyone else like how using $IF/THEN works as a block REM statement?
    'I mean I'd rather we had a QB64 block remark statement like the one used for JavaScript, but thi hack will do.
$End If

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

'#END
Newer polygon test, I think introduced a bug? "polygon35.bas" :

Code: (Select All)
Dim Shared m_sTitle As String: m_sTitle = "2D Vector Shapes v0.33 by madscijr"
_Title m_sTitle ' display in the Window's title bar

' Simple test of vector graphics,
' borrowed graphics objects and format from Widescreen Asteroids by Terry Ritchie.

' DATE         WHO-DONE-IT   DID-WHAT
' 2022-11-22   madscijr      Version 0.34 (last version before project stalled!)
' 2025-01-05   madscijr      Version 0.35 (no changes, just refamiliarize with code and get project back on track)

' DONE
' * We now can draw opaque shapes (still need to get working for shapes with non-contiguous areas)
' * Display looks prettier!
'   - Shapes not active are drawn in an animated dashes
'   - Text display is color coded to shapes
' * Tried adding twinkling stars (hard to see, need to fix)
' * fix twinkling stars

' TO DO:
' * get fill working for shapes with non-contiguous areas)
'   - define "fill coordinates" property or array
'   - auto find these in shape
'   - eliminate duplicates
'   - store the remaining values store in shape data
'   - use for PAINT when drawing fill color

' BOOLEAN CONSTANTS
Const FALSE = 0
Const TRUE = Not FALSE

' OTHER SETTINGS
Const cFPS = 120
Const cMinStars = 50
Const cMaxStars = 150
Const cSpeed = 2

' 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
    z As Integer ' zorder in which object is stacked (in front of or behind other objects)
    FillColor As _Unsigned Long ' color to fill in object with (use cEmpty for transparent)
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 LOCATION + COLOR OF EACH STAR
Type StarType
    x As Integer
    y As Integer
   
    ColorIndex As _Unsigned Long ' the star's current color
    TwinkleCounter As Integer ' counter for twinkles
    MaxTwinkCount As Integer ' controls how fast the star twinkles
   
    width As Integer
    MinWidth As Integer ' smallest width
    MaxWidth As Integer ' largest width
    WidthCounter As Integer ' counter for width
    MaxWidthCount As Integer ' controls how fast the star size fluctuates
    BigCounter As Integer ' counter for max width
    MaxBigCount As Integer ' controls how long the star stays big
End Type ' StarType

' OBJECT VARIABLES
ReDim Shared m_arrObject(1 To 6) As ObjectType
ReDim Shared m_arrOrder(1 To 6) As Integer
ReDim Shared m_arrLines(1 To 8, 1 To 32) As CoordType
ReDim Shared m_arrColor(1 To 6) As _Unsigned Long
ReDim Shared m_arrLineStyle(1 To 8) As Long
ReDim Shared m_arrStars(1 To cMaxStars) As StarType
ReDim Shared m_arrGrayColor(-1) As _Unsigned Long

' =============================================================================
' START THE MAIN ROUTINE
'DrawVectorObjectTest1
EditVectorObject1

' =============================================================================
' FINISH
Screen 0
System ' return control to the operating system


' /////////////////////////////////////////////////////////////////////////////
' NOTE: this version does NOT change the screen to 1280x1024
'       but it might depend on that resolution.
'       Screen would be set to 1280x1024 at the top of the program.

Sub EditVectorObject1
    Dim RoutineName As String: RoutineName = "EditVectorObject1"
    Dim iX1 As Integer: iX1 = 0
    Dim iY1 As Integer: iY1 = 0
    Dim iOldX1 As Integer: iOldX1 = 0
    Dim iOldY1 As Integer: iOldY1 = 0
    Dim bLeftClick As Integer: bLeftClick = FALSE
    Dim bRightClick As Integer: bRightClick = FALSE
    Dim bMiddleClick As Integer: bMiddleClick = FALSE
    Dim bOldLeftClick As Integer: bOldLeftClick = FALSE
    Dim bOldRightClick As Integer: bOldRightClick = FALSE
    Dim bOldMiddleClick As Integer: bOldMiddleClick = FALSE
    Dim iX2 As Integer: iX2 = _Width / 2
    Dim iY2 As Integer: iY2 = _Height / 2
    Dim iOldX2 As Integer: iOldX2 = 0
    Dim iOldY2 As Integer: iOldY2 = 0
    Dim iColor1 As _Unsigned Long: iColor1 = cRed
    Dim iColor2 As _Unsigned Long: iColor2 = cLime

    ' Screen set at top of program, before main menu.
    Screen _NewImage(1280, 1024, 32): _ScreenMove 0, 0
    Cls
   
    _MouseHide ' hide OS mouse pointer
    Do While _MouseInput: Loop
    Do
        Color cWhite, cBlack
        If iOldX1 <> iX1 Or iOldY1 <> iY1 Then _PrintString (iOldX1, iOldY1), " "
        If iOldX2 <> iX2 Or iOldY2 <> iY2 Then _PrintString (iOldX2, iOldY2), " "

        Color cWhite, cEmpty
        PrintString 0, 0, "_MOUSEX        ="
        PrintString 1, 0, "_MOUSEY        ="
        PrintString 2, 0, "_MOUSEBUTTON(1)="
        PrintString 3, 0, "_MOUSEBUTTON(2)="
        PrintString 4, 0, "_MOUSEBUTTON(3)="
        PrintString 5, 0, "_MOUSEWHEEL X  ="
        PrintString 6, 0, "_MOUSEWHEEL Y  ="
        PrintString 8, 0, "PRESS <ESC> TO EXIT"

        Color cWhite, cBlack
        PrintString 0, 16, Left$(cstr$(iX1) + "     ", 5)
        PrintString 1, 16, Left$(cstr$(iY1) + "     ", 5)
        PrintString 2, 16, Left$(TrueFalse$(bLeftClick) + "     ", 5)
        PrintString 3, 16, Left$(TrueFalse$(bRightClick) + "     ", 5)
        PrintString 4, 16, Left$(TrueFalse$(bMiddleClick) + "     ", 5)
        PrintString 5, 16, Left$(cstr$(iX2) + "     ", 5)
        PrintString 6, 16, Left$(cstr$(iY2) + "     ", 5)

        Color cBlack, iColor1: _PrintString (iX1, iY1), " "
        iOldX1 = iX1: iOldY1 = iY1

        Color cBlack, iColor2: _PrintString (iX2, iY2), " "
        iOldX2 = iX2: iOldY2 = iY2

        iX1 = (_MouseX \ _FontWidth) * _FontWidth
        iY1 = (_MouseY \ _FontHeight) * _FontHeight

        bLeftClick = _MouseButton(1)
        If bLeftClick Then
            If bOldLeftClick = FALSE Then
                If iColor1 = cOrangeRed Then
                    iColor1 = cRed
                ElseIf iColor1 = cRed Then
                    iColor1 = cMagenta
                Else
                    iColor1 = cOrangeRed
                End If
                bOldLeftClick = TRUE
            End If
        Else
            bOldLeftClick = FALSE
        End If

        bRightClick = _MouseButton(2)
        If bRightClick Then
            If bOldRightClick = FALSE Then
                If iColor2 = cBlue Then
                    iColor2 = cLime
                ElseIf iColor2 = cLime Then
                    iColor2 = cYellow
                Else
                    iColor2 = cBlue
                End If
                bOldRightClick = TRUE
            End If
        Else
            bOldRightClick = FALSE
        End If

        bMiddleClick = _MouseButton(3)

        Do While _MouseInput
            If bMiddleClick Then
                iY2 = iY2 + (_MouseWheel * _FontHeight) ' -1 up, 0 no movement, 1 down
            Else
                iX2 = iX2 + (_MouseWheel * _FontWidth) ' -1 up, 0 no movement, 1 down
            End If
        Loop

        If iY2 < 1 Then iY2 = 1
        If iY2 > (_Height - _FontHeight) Then iY2 = (HEIGHT - _FontHeight)

    Loop Until _KeyDown(27)
    _KeyClear

    _MouseShow "default": _Delay 0.5
   
    ' Screen 0 set at end of program up top.
    Screen 0
   
End Sub ' EditVectorObject1

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

Sub DrawVectorObjectTest1
    ' LOCAL VARIABLES
    Dim iFPS As Integer: iFPS = cFPS
    Dim iLoop As Integer
    Dim iObject As Integer
    Dim iLayer As Integer
    Dim iLine As Integer
    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 iStyleCountMax As Integer: iStyleCountMax = iFPS \ 12 ' change 4x a second
    Dim iStyleCountNext As Integer: iStyleCountNext = 0
    Dim iLineStyleIndex As Integer
    Dim lngLineStyle ' line style
    Dim lngDashedLineStyle ' line style for other objects
    Dim lngSolidLineStyle ' selected object's line style
    Dim iNumStars As Integer
    Dim iValue As Integer
    Dim iMinValue As Integer
    Dim iMaxValue As Integer
    Dim iStarLoop As Integer
    Dim x1%
    Dim x2%
    Dim y1%
    Dim y2%
    Dim imgStars& ' used for drawing background
    Dim imgText& ' used for drawing text
    Dim imgObjects& ' used for drawing objects
    Dim imgTemp& ' temporary drawing area
   
    ' =============================================================================
    ' INITIALIZE
    InitializeRandom
    Screen _NewImage(iMaxX, iMaxY, 32) ' 100 text columns x 40 text rows
   
    ' USE LATER FOR DRAWING LAYERS:
    imgStars& = _NewImage(iMaxX, iMaxY, 32) ' background stars
    imgText& = _NewImage(iMaxX, iMaxY, 32) ' text
    imgObjects& = _NewImage(iMaxX, iMaxY, 32) ' frontground objects
    imgTemp& = _NewImage(iMaxX, iMaxY, 32) ' temporary
   
    ' =============================================================================
    ' START NEW GAME
    Do
        _KeyClear

        ' CONFIGURE PRINTING FOR _PrintString
        _PrintMode _FillBackground
        '_PrintMode _KEEPBACKGROUND

        ' INIT OBJECT DEFINITIONS
        InitVectorObjects
       
        ' INIT VARS
        sKey = ""
       
        ' PLACE OBJECTS
        iX = 0: iY = 0
        iValue = UBound(m_arrObject)
        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
            m_arrObject(iObject).z = iValue
            m_arrObject(iObject).FillColor = cBlack
           
            iX = iX + 200
            If iX > 800 Then
                iX = 0
                iY = iY + 200
                If iY > 640 Then
                    iY = 0
                End If
            End If
            iValue = iValue - 1
        Next iObject
       
        ' INIT OBJECT Z-ORDER ARRAY
        ReDim m_arrOrder(lbound(m_arrObject) To ubound(m_arrObject)) As Integer
        For iObject = LBound(m_arrObject) To UBound(m_arrObject)
            m_arrOrder( m_arrObject(iObject).z ) = iObject
        Next iObject
       
        ' SAVE LINE STYLE SEQUENCE
        iLoop = 0
        iLoop = iLoop + 1: m_arrLineStyle(iLoop) = 61680 ' 1111000011110000
        iLoop = iLoop + 1: m_arrLineStyle(iLoop) = 30840 ' 0111100001111000
        iLoop = iLoop + 1: m_arrLineStyle(iLoop) = 15420 ' 0011110000111100
        iLoop = iLoop + 1: m_arrLineStyle(iLoop) = 7710 ' 0001111000011110
        iLoop = iLoop + 1: m_arrLineStyle(iLoop) = 3855 ' 0000111100001111
        iLoop = iLoop + 1: m_arrLineStyle(iLoop) = 34695 ' 1000011110000111
        iLoop = iLoop + 1: m_arrLineStyle(iLoop) = 50115 ' 1100001111000011
        iLoop = iLoop + 1: m_arrLineStyle(iLoop) = 57825 ' 1110000111100001
        iLineStyleIndex = LBound(m_arrLineStyle)
        lngDashedLineStyle = m_arrLineStyle(iLineStyleIndex)
        lngSolidLineStyle = 65535

        ' SAVE COLORS FOR EACH OBJECT (FROM FIRST LINE SEGMENT) TO ARRAY FOR TEXT
        For iObject = LBound(m_arrObject) To UBound(m_arrObject)
            iLine = LBound(m_arrLines, 2)
            m_arrColor(iObject) = m_arrLines(iObject, iLine).color
        Next iObject
       
        ' SAVE COLORS FOR STARS AND GRAYSCALE OBJECTS
        AddGrayscaleColors m_arrGrayColor()
       
        ' -----------------------------------------------------------------------------
        ' PLACE STARS RANDOMLY
        iNumStars = RandomNumber%(cMinStars, cMaxStars)
        ReDim m_arrStars(1 To iNumStars) As StarType
        For iLoop = 1 To iNumStars
            m_arrStars(iLoop).x = RandomNumber%(iMinX, iMaxX)
            m_arrStars(iLoop).y = RandomNumber%(iMinY, iMaxY)
            m_arrStars(iLoop).ColorIndex = RandomNumber%(LBound(m_arrGrayColor), UBound(m_arrGrayColor))
           
            ' Assign a width 1-3 (with different probability for each)
            iValue = RandomNumber%(1, 100)
            If iValue > 98 Then
                m_arrStars(iLoop).MinWidth = RandomNumber%(2, 3)
                m_arrStars(iLoop).MaxWidth = 3
            ElseIf iValue > 85 Then
                m_arrStars(iLoop).MinWidth = RandomNumber%(1, 2)
                m_arrStars(iLoop).MaxWidth = 2
            Else
                m_arrStars(iLoop).MinWidth = RandomNumber%(0, 1)
                m_arrStars(iLoop).MaxWidth = 1
            End If
            ' Set initial width to normal (MaxWidth)
            m_arrStars(iLoop).width = m_arrStars(iLoop).MaxWidth
           
            ' Determine how quickly size changes
            ' Anywhere between 1/30 second and 1 seconds
            iMinValue = iFPS \ 30
            iMaxValue = iFPS
            m_arrStars(iLoop).MaxWidthCount = RandomNumber%(iMinValue, iMaxValue)
            m_arrStars(iLoop).WidthCounter = 0
           
            ' Determine how long size is changed
            ' Anywhere between 1/100 second and 1/50 seconds
            iMinValue = iFPS \ 100
            iMaxValue = iFPS \ 50
            m_arrStars(iLoop).MaxBigCount = RandomNumber%(iMinValue, iMaxValue)
            m_arrStars(iLoop).BigCounter = 0
           
            ' Determine how quickly they twinkle
            ' Anywhere between 1/120 second and 1/20 seconds
            iMinValue = iFPS \ 120
            iMaxValue = iFPS \ 20
            m_arrStars(iLoop).MaxTwinkCount = RandomNumber%(iMinValue, iMaxValue)
            m_arrStars(iLoop).TwinkleCounter = 0
        Next iLoop
       
        ' ================================================================================================================================================================
        ' BEGIN MAIN LOOP
        While TRUE = TRUE
           
            ' CLEAR OBJECTS LAYER
            _Dest imgObjects&: Cls , cEmpty
           
            ' MOVE AND ADD ENABLED OBJECTS (IN STACKING ORDER)
            For iLayer = UBound(m_arrOrder) To LBound(m_arrOrder) Step -1
                ' Get next object
                iObject = m_arrOrder(iLayer)
               
                ' 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 - cSpeed
                            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 + cSpeed
                            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 - cSpeed
                            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 + cSpeed
                            If m_arrObject(iObject).y > iMaxY Then
                                m_arrObject(iObject).y = iMinY
                            End If
                        End If
                    End If
                   
                    ' Clear temporary (layer and draw on it
                    _Dest imgTemp&: Cls , cEmpty
                   
                    ' Draw object's line segments
                    For iLine = LBound(m_arrLines, 2) To UBound(m_arrLines, 2)
                        If m_arrLines(iObject, iLine).IsLast = FALSE Then
                            'Line (x1%, y1%)-(x2%, y2%), arrColor(arrStars(iLoop).ColorIndex), BF, lngLineStyle
                            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, , lngSolidLineStyle
                        Else
                            Exit For
                        End If
                    Next iLine
                   
                    ' Draw fill color if not transparent
                    if m_arrObject(iObject).FillColor <> cEmpty then
                        ' Fill in current object with its fill color...
                        'PAINT [STEP] (column%, row%), fillColor[, borderColor%]
                        Paint (m_arrObject(iObject).x, m_arrObject(iObject).y), _
                            m_arrObject(iObject).FillColor, m_arrColor(iObject)
                    end if
                   
                    ' Make other objects appear drawn with a dashed line
                    If iObject <> iWhich Then
                        ' Outline with a dashed line
                        For iLine = LBound(m_arrLines, 2) To UBound(m_arrLines, 2)
                            If m_arrLines(iObject, iLine).IsLast = FALSE Then
                                'Line (x1%, y1%)-(x2%, y2%), arrColor(arrStars(iLoop).ColorIndex), BF, lngLineStyle
                                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) _
                                    , _
                                    cBlack, , lngDashedLineStyle
                            Else
                                Exit For
                            End If
                        Next iLine
                    End If
                   
                    ' Add new object to objects layer
                    '_PUTIMAGE , sourceHandle&, destHandle& 'size full source to fit full destination area
                    _PutImage , imgTemp&, imgObjects&
                   
                End If
            Next iLayer
           
            ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
            ' BEGIN STARS
           
            ' Twinkle twinkle little stars
            _Dest imgStars&: Cls , cEmpty
            For iStarLoop = LBound(m_arrStars) To UBound(m_arrStars)
                ' increment twinkle counter
                m_arrStars(iStarLoop).TwinkleCounter = m_arrStars(iStarLoop).TwinkleCounter + 1
               
                ' is it time to twinkle the color?
                If m_arrStars(iStarLoop).TwinkleCounter > m_arrStars(iStarLoop).MaxTwinkCount Then
                    m_arrStars(iStarLoop).TwinkleCounter = 0 ' reset counter
                    m_arrStars(iStarLoop).ColorIndex = m_arrStars(iStarLoop).ColorIndex + 1 ' increment color
                    If m_arrStars(iStarLoop).ColorIndex > UBound(m_arrGrayColor) Then
                        m_arrStars(iStarLoop).ColorIndex = LBound(m_arrGrayColor) ' reset color
                    End If
                End If
               
                ' increment width counter
                if m_arrStars(iStarLoop).BigCounter = 0 then
                    m_arrStars(iStarLoop).WidthCounter = m_arrStars(iStarLoop).WidthCounter + 1
                   
                    ' is it time to fluctuate the width
                    If m_arrStars(iStarLoop).WidthCounter > m_arrStars(iStarLoop).MaxWidthCount Then
                        m_arrStars(iStarLoop).WidthCounter = 0 ' reset counter
                        m_arrStars(iStarLoop).BigCounter = 1 ' start big counter
                        m_arrStars(iStarLoop).width = m_arrStars(iStarLoop).MinWidth ' twinkle width
                    Else
                        m_arrStars(iStarLoop).width = m_arrStars(iStarLoop).MaxWidth ' normal width
                    End If
                else
                    ' increment big counter
                    m_arrStars(iStarLoop).BigCounter = m_arrStars(iStarLoop).BigCounter + 1
                   
                    ' is it time to return to normal size?
                    If m_arrStars(iStarLoop).BigCounter > m_arrStars(iStarLoop).MaxBigCount Then
                        m_arrStars(iStarLoop).BigCounter = 0 ' reset counter
                        m_arrStars(iStarLoop).width = m_arrStars(iStarLoop).MaxWidth ' normal width
                    End If
                end if
               
                ' get size
                x1% = m_arrStars(iStarLoop).x: x2% = x1% + m_arrStars(iStarLoop).width
                y1% = m_arrStars(iStarLoop).y: y2% = y1% + m_arrStars(iStarLoop).width
               
                ' (re)draw it
                Line (x1%, y1%)-(x2%, y2%), m_arrGrayColor(m_arrStars(iStarLoop).ColorIndex), BF
            Next iStarLoop
            ' END STARS
            ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
           
            ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
            ' SHOW TEXT
            _Dest imgText&: Cls , cEmpty
            DrawText sKey, iWhich
            ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
           
            ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
            ' BEGIN UPDATE SCREEN
           
            ' COPY LAYERS TO SCREEN
            _Dest 0: Cls , cBlack
            If imgStars& < -1 Then
                _PutImage , imgStars&, 0
            End If
            If imgText& < -1 Then
                _PutImage , imgText&, 0
            End If
            If imgObjects& < -1 Then
                _PutImage , imgObjects&, 0
            End If
           
            ' UPDATE THE SCREEN
            _Display
           
            ' END UPDATE SCREEN
            ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
           
            ' 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

            ' CYCLE LINE STYLE FOR ACTIVE OBJECT
            iStyleCountNext = iStyleCountNext + 1 ' increment line style counter
            If iStyleCountNext > iStyleCountMax Then
                iStyleCountNext = 0
                iLineStyleIndex = iLineStyleIndex + 1
                If iLineStyleIndex > UBound(m_arrLineStyle) Then
                    iLineStyleIndex = LBound(m_arrLineStyle)
                End If
                lngDashedLineStyle = m_arrLineStyle(iLineStyleIndex)
            End If

            ' CLEAR KEYBOARD BUFFER
            _KeyClear

            ' CONTROL GAME SPEED
            _Limit iFPS
        Wend
        ' END MAIN LOOP
        ' ================================================================================================================================================================

        ' 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
   
    ' CLEAR IMAGES
    screen 0
    IF imgStars& < -1 THEN _FREEIMAGE imgStars&
    IF imgText& < -1 THEN _FREEIMAGE imgText&
    IF imgObjects& < -1 THEN _FREEIMAGE imgObjects&
    IF imgTemp& < -1 THEN _FREEIMAGE imgTemp&
   
    ' RETURN TO AUTODISPLAY
    _AutoDisplay

End Sub ' DrawVectorObjectTest1

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

' Receives:
' Long iInput1 = value (0-65535) to conver to binary

' Returns a 16 character string of "1" and "0"
' (a 16-bit binary representation of iInput1)

Function BinaryStringFromLong$ (iInput1 As Long)
    Dim sBinary As String
    Dim iInput As Long
    Dim iLoop As Integer
    Dim iNextValue As Long

    sBinary = ""
    iInput = iInput1
    If iInput >= 0 Then
        For iLoop = 15 To 0 Step -1
            iNextValue = 2 ^ iLoop
            If (iInput \ iNextValue) > 0 Then
                sBinary = sBinary + "1"
            Else
                sBinary = sBinary + "0"
            End If
            iInput = iInput Mod iNextValue
        Next iLoop
    End If

    BinaryStringFromLong$ = sBinary
End Function ' BinaryStringFromLong$

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

' Receives:
' String sBitPattern = 16 character string of "1" and "0"
'                      (a 16-bit binary representation)

' Returns the bit pattern converted to a long integer.

Function LongFromBinaryString& (sBitPattern As String)
    Dim sInput As String: sInput = sBitPattern
    Dim iLoop As Integer
    Dim MyLong As Long

    MyLong = 0

    If Len(sInput) >= 16 Then
        For iLoop = 0 To 15
            If Mid$(sInput, 16 - iLoop, 1) = "1" Then
                MyLong = MyLong + (2 ^ iLoop)
            End If
        Next iLoop
    End If

    LongFromBinaryString& = MyLong
End Function ' LongFromBinaryString&

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

' future versions will pull this data from an editable file

Sub 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
            m_arrLines(iObject, iLine).IsLast = TRUE
            m_arrObject(iObject).IsEnabled = TRUE
            Exit For
        ElseIf b1 = -254 Then
            m_arrLines(iObject, iLine).IsLast = TRUE
            m_arrObject(iObject).IsEnabled = TRUE
            iObject = iObject + 1
            iLine = 1
            If iObject > UBound(m_arrLines, 1) Then Exit For
        Else
            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
            If iLine > UBound(m_arrLines, 2) Then Exit For
        End If
    Next iLoop%

    VectorData:
    '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 = yellow
    Data 9,-62,60,-21,255,255,0
    Data 60,-21,62,-3,255,255,0
    Data 62,-3,24,13,255,255,0
    Data 24,13,53,34,255,255,0
    Data 53,34,38,55,255,255,0
    Data 38,55,20,40,255,255,0
    Data 20,40,-37,61,255,255,0
    Data -37,61,-63,15,255,255,0
    Data -63,15,-57,-24,255,255,0
    Data -57,-24,-24,-24,255,255,0
    Data -24,-24,-38,-45,255,255,0
    Data -38,-45,9,-62,255,255,0
    Data 0,0,0,0,-254,-254,-254

    'objmouse = white
    Data 0,-10,6,3,255,255,255
    Data 6,3,1,2,255,255,255
    Data 1,2,1,10,255,255,255
    Data 1,10,-1,10,255,255,255
    Data -1,10,-1,2,255,255,255
    Data -1,2,-6,3,255,255,255
    Data -6,3,0,-10,255,255,255
    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
    'Data 0,18,0,18,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

' /////////////////////////////////////////////////////////////////////////////
' SHOW INSTRUMENTS + INSTRUCTIONS
' 800x600 = 40 rows x 100 columns

sub DrawText( _
    sKey as string, _
    iWhich as integer _
    )

    Dim iObject As Integer
    Dim RowNum As Integer
    Dim sFlag As String
    Dim iNum As Integer
   
    RowNum = 0

    Color cWhite, cEmpty
    RowNum = RowNum + 1: PrintAt RowNum, 1, m_sTitle
    RowNum = RowNum + 1
   
    Color cYellow, cEmpty
    RowNum = RowNum + 1: PrintAt RowNum, 1, "----------------------------------------"
    RowNum = RowNum + 1: PrintAt RowNum, 1, "Press 1-6 to select active object."
    RowNum = RowNum + 1: PrintAt RowNum, 1, "Arrow keys move active object."
    RowNum = RowNum + 1: PrintAt RowNum, 1, "Press ESC to quit"
    RowNum = RowNum + 1: PrintAt RowNum, 1, "----------------------------------------"
    RowNum = RowNum + 1

    ' SHOW OBJECTS
    Color cGray, cEmpty
    RowNum = RowNum + 1: PrintAt RowNum, 1, "----------------------------------------"

    iNum = 0
    For iObject = LBound(m_arrObject) To UBound(m_arrObject)
        iNum = iNum + 1
        If m_arrObject(iObject).IsEnabled = TRUE Then
            Color m_arrColor(iObject), cEmpty
            If iObject = iWhich Then
                'Color cCyan, cEmpty
                sFlag = "-> "
            Else
                'Color cDodgerBlue, cEmpty
                sFlag = "   "
            End If
        Else
            Color cGray, cEmpty
            sFlag = "   "
        End If

        RowNum = RowNum + 1: PrintAt RowNum, 1, "" + _
            sFlag + _
            cstr$(iNum) + ". " + _
            "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) + ")" + _
            ""
    Next iObject

    Color cGray, cEmpty
    RowNum = RowNum + 1: PrintAt RowNum, 1, "----------------------------------------"

    RowNum = RowNum + 2

    '' SHOW ACTIVE OBJECT
    'Color cWhite
    'RowNum = RowNum + 1: PrintAt RowNum, 1, "Object #   : " + cstr$(iWhich)

    ' SHOW INPUT
    Color cLime, cEmpty
    RowNum = RowNum + 1: PrintAt RowNum, 1, "Controls   : " + RightPadString$(sKey, 10, " ") + "   "
   
End Sub ' DrawText

' ################################################################################################################################################################
' BEGIN COLOR ARRAY FUNCTIONS #COLR
' ################################################################################################################################################################

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

Sub AddColor (ColorValue As _Unsigned Long, arrColor() As _Unsigned Long)
    ReDim _Preserve arrColor(0 To UBound(arrColor) + 1) As _Unsigned Long
    arrColor(UBound(arrColor)) = ColorValue
End Sub ' AddColor

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

Sub AddColors (ColorValue As _Unsigned Long, arrColor() As _Unsigned Long, HowMany As Long)
    dim iLoop as integer
    for iLoop = 1 to HowMany
        AddColor ColorValue, arrColor()
    next iLoop
End Sub ' AddColors

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

Sub AddGrayscaleColors (arrColor() As _Unsigned Long)
    Dim iNum As Integer
    iNum = 1
    AddColors cDimGray, arrColor(), iNum
    AddColors cGray, arrColor(), iNum
    AddColors cDarkGray, arrColor(), iNum
    AddColors cSilver, arrColor(), iNum
    AddColors cLightGray, arrColor(), iNum
    AddColors cGainsboro, arrColor(), iNum
    AddColors cWhiteSmoke, arrColor(), iNum
    AddColors cWhite, arrColor(), iNum '* 2
    AddColors cWhiteSmoke, arrColor(), iNum
    AddColors cGainsboro, arrColor(), iNum
    AddColors cLightGray, arrColor(), iNum
    AddColors cSilver, arrColor(), iNum
    AddColors cDarkGray, arrColor(), iNum
    AddColors cGray, arrColor(), iNum
End Sub ' AddGrayscaleColors

' ################################################################################################################################################################
' END COLOR ARRAY FUNCTIONS @COLR
' ################################################################################################################################################################

' ################################################################################################################################################################
' 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)
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 = _RGB32(0, 0, 0, 0)
End Function ' cEmpty~&

' ################################################################################################################################################################
' END COLOR FUNCTIONS @COLOR
' ################################################################################################################################################################

' ################################################################################################################################################################
' BEGIN KEYBOARD CODES 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 CODES FUNCTIONS @KEYCODE
' ################################################################################################################################################################

' ################################################################################################################################################################
' 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$

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

$If  Then
    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$
$End If

' /////////////////////////////////////////////////////////////////////////////
' 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

' /////////////////////////////////////////////////////////////////////////////
' 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$

' /////////////////////////////////////////////////////////////////////////////
' 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

' /////////////////////////////////////////////////////////////////////////////
' 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

' /////////////////////////////////////////////////////////////////////////////
' Draws scaled + rotated text to screen
' by BPlus

' Scale rotate font text strings by B+
' https://qb64phoenix.com/forum/showthread.php?tid=414&highlight=rotate+text

' INPUT:
' S$ is the string to display
' c is the color (will have a transparent background)
' midX and midY is the center of where you want to display the string
' xScale would multiply 8 pixel width of default font
' yScale would multiply the 16 pixel height of the default font
' Rotation is in Radian units, use _D2R to convert Degree units to Radian units

' DEPENDENCIES:
' drwString needs sub RotoZoom2, intended for graphics screens using the default font.

Sub drwString (S$, c As _Unsigned Long, midX, midY, xScale, yScale, Rotation As Single)
    I& = _NewImage(_PrintWidth(S$), _FontHeight, 32)
    _Dest I&
    Color c, _RGBA32(0, 0, 0, 0)
    _PrintString (0, 0), S$
    _Dest storeDest&
    RotoZoom2 midX, midY, I&, xScale, yScale, Rotation
    _FreeImage I&
End Sub ' drwString

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

Sub DumpScreenAndFontSize ()
    Dim iCols As Integer
    Dim iRows As Integer
    'Screen _NewImage(1024, 768, 32): _ScreenMove 0, 0
    iCols = _Width(0) \ _FontWidth
    iRows = _Height(0) \ _FontHeight
    Print "_Width(0)  =" + _Trim$(Str$(_Width(0)))
    Print "_Height(0) =" + _Trim$(Str$(_Height(0)))
    Print "_FontWidth =" + _Trim$(Str$(_FontWidth))
    Print "_FontHeight=" + _Trim$(Str$(_FontHeight))
    Print "iCols = _Width(0)  \ _FontWidth  = " + _Trim$(Str$(iCols))
    Print "iRows = _Height(0) \ _FontHeight = " + _Trim$(Str$(iRows))
End Sub ' DumpScreenAndFontSize

' /////////////////////////////////////////////////////////////////////////////
' 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

' /////////////////////////////////////////////////////////////////////////////
' Gets the # of seconds so far today

Function GetTimeSeconds& ()
    Dim result&: result& = 0
    Dim sTime$: sTime$ = Time$
    Dim sHH24$: sHH24$ = Mid$(sTime$, 1, 2)
    Dim sMI$: sMI$ = Mid$(sTime$, 4, 2)
    Dim sSS$: sSS$ = Mid$(sTime$, 7, 2)
   
    result& = result& + VAL(sSS$)
    result& = result& + (VAL(sMI$) * 60)
    result& = result& + ( (VAL(sHH24$) * 60) * 60)
   
    ' RETURN RESULT
    GetTimeSeconds& = result&
End Function ' GetTimeSeconds&

' /////////////////////////////////////////////////////////////////////////////
' 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

' /////////////////////////////////////////////////////////////////////////////
' iNum% = PromptForIntegerInRange%("Please type a number between {min} and {max} (or blank to quit).", 1, 4, 0) ' prompt, min, max, default

Function PromptForIntegerInRange% (sPrompt$, iMin%, iMax%, iDefault%)
    Dim iValue%
    Dim bFinished%
    Dim sPrompt1$
    Dim in$

    If Len(sPrompt$) > 0 Then
        sPrompt1$ = sPrompt$
    Else
        sPrompt1$ = "Please type a number between {min} and {max} (or blank to quit)."
    End If

    sPrompt1$ = Replace$(sPrompt1$, "{min}", cstr$(iMin%))
    sPrompt1$ = Replace$(sPrompt1$, "{max}", cstr$(iMax%))

    bFinished% = FALSE
    Do
        Print sPrompt1$

        Input in$
        in$ = _Trim$(in$)
        If Len(in$) > 0 Then
            If IsNumber(in$) Then
                iValue% = Val(in$)
                If iValue% >= iMin% And iValue% <= iMax% Then
                    'bFinished% = TRUE
                    Exit Do
                Else
                    Print "Number out of range."
                    Print
                End If
            Else
                Print "Not a valid number."
                Print
            End If
        Else
            iValue% = iDefault%
            Exit Do
            'bFinished% = TRUE
        End If
    Loop Until bFinished% = TRUE

    PromptForIntegerInRange% = iValue%
End Function ' PromptForIntegerInRange%

' /////////////////////////////////////////////////////////////////////////////
' 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

' /////////////////////////////////////////////////////////////////////////////
' Initializes random number generator seed

' NOTE:
' using # of seconds so far in the day
' may be slightly more random than Randomize Timer
' unless user runs program at exact same time every day

Sub InitializeRandom
    dim iSeed As Integer
   
    'iSeed = GetTimeSeconds& MOD 32767
   
    t9# = (Timer * 1000000) Mod 32767
   
    Randomize iSeed
    'print "Randomize " + cstr$(iSeed)
    'Sleep
End Sub ' InitializeRandom

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

' Note: random-number generator should be initialized with
'       InitializeRandom or Randomize Timer

Function RandomNumber% (Min%, Max%)
    Dim NumSpread%
    NumSpread% = (Max% - Min%) + 1
    RandomNumber% = Int(Rnd * NumSpread%) + Min% ' GET RANDOM # BETWEEN Max% AND Min%
End Function ' RandomNumber%

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

$If  Then
    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
$End If

' /////////////////////////////////////////////////////////////////////////////
' 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$

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

$If  Then
    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
$End If

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

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

' /////////////////////////////////////////////////////////////////////////////
' This sub gives really nice control over displaying an Image.
' by BPlus

' Scale rotate font text strings by B+
' https://qb64phoenix.com/forum/showthread.php?tid=414&highlight=rotate+text

' USED BY: drwString

Sub RotoZoom2 (centerX As Long, centerY As Long, Image As Long, xScale As Single, yScale As Single, Rotation As Single)
    Dim px(3) As Single: Dim py(3) As Single
    W& = _Width(Image&): H& = _Height(Image&)
    px(0) = -W& / 2: py(0) = -H& / 2: px(1) = -W& / 2: py(1) = H& / 2
    px(2) = W& / 2: py(2) = H& / 2: px(3) = W& / 2: py(3) = -H& / 2
    sinr! = Sin(-Rotation): cosr! = Cos(-Rotation)
    For i& = 0 To 3
        x2& = (px(i&) * cosr! + sinr! * py(i&)) * xScale + centerX: y2& = (py(i&) * cosr! - px(i&) * sinr!) * yScale + centerY
        px(i&) = x2&: py(i&) = y2&
    Next
    _MapTriangle (0, 0)-(0, H& - 1)-(W& - 1, H& - 1), Image& To(px(0), py(0))-(px(1), py(1))-(px(2), py(2))
    _MapTriangle (0, 0)-(W& - 1, 0)-(W& - 1, H& - 1), Image& To(px(0), py(0))-(px(3), py(3))-(px(2), py(2))
End Sub ' RotoZoom2

'' /////////////////////////////////////////////////////////////////////////////
'' https://qb64phoenix.com/forum/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%

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

$If  Then
    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
$End If

' /////////////////////////////////////////////////////////////////////////////
' 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

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

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

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

$If  Then
    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
$End If

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

$If  Then
    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
$End If

' /////////////////////////////////////////////////////////////////////////////
' 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 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%

' Screen _NewImage(1024, 768, 32): _ScreenMove 0, 0
' iCols = _Width(0) \ _FontWidth
' iRows = _Height(0) \ _FontHeight
' Print "_Width(0)  =" + _Trim$(Str$(_Width(0)))
' Print "_Height(0) =" + _Trim$(Str$(_Height(0)))
' Print "_FontWidth =" + _Trim$(Str$(_FontWidth))
' Print "_FontHeight=" + _Trim$(Str$(_FontHeight))
' Print "iCols = _Width(0)  \ _FontWidth  = " + _Trim$(Str$(iCols))
' Print "iRows = _Height(0) \ _FontHeight = " + _Trim$(Str$(iRows))

$If  Then
    'Pete:
    'Oh, does anyone else like how using $IF/THEN works as a block REM statement?
    'I mean I'd rather we had a QB64 block remark statement like the one used for JavaScript, but thi hack will do.
$End If

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

'#END
Reply




Users browsing this thread: 2 Guest(s)