' --- Spawn flame particles above the text --- Dim tw AsLong
tw = _PrintWidth(txt$)
For i = 1To MAXP ' number of flames per frame
emitY = (y - _FontHeight / 4) + Rnd * (1.25 * _FontHeight) SpawnFlame x + Rnd * tw, emitY, 0 If i Mod20 = 0Then'I don't want as many little firework style flame explosions
emitY = (y - _FontHeight / 4) + Rnd * (.5 * _FontHeight) SpawnFlame x + Rnd * tw, emitY, -1 End If Next
For i = 1To MAXP If FP(i).life > 0Then
FP(i).x = FP(i).x + FP(i).dx
FP(i).y = FP(i).y + FP(i).dy
FP(i).life = FP(i).life - 1 End If Next
For i = 1To MAXP If FP(i).life > 0Then
t = FP(i).life / FP(i).maxlife
c = _RGB(255 * t, 120 * t, 20 * t) ' Flame color gradient: yellow ? orange ? red ? dark Line (FP(i).x, FP(i).y)-Step(2, 3), c, BF End If Next End Sub '=========================================================== Screen_NewImage(800, 600, 32)
So I was playing around with trying to produce some sort of flaming text using particles and such. What I've came up with is the above. It's... not bad, but quite the "flame" I was looking for.
Still, it's a place to start with, and since you guys like various challenges and such, here's one for you: Make your own flaming text routine. Let's see what you guys can come up with. I've always been one who liked to do various things with text (I have rainbow text routines, and circle text, and rotating text, and scaling text, and faux-3d text, and lots of others), so I figured "Why not shoot for something with a flame style effect?"
Here's my go at that attempt. Let's see what you guys can come up with. Everybody needs a nice flaming text routine -- if just for a title screen or something sometime! Let's go guys! Show me your best (and worst so I don't feel so bad).
@Pete is disqualified, as I know his version already:
Code: (Select All)
SCREEN 0
PRINT "My text is";
COLOR 20
PRINT " FLAMING ";
COLOR 7
PRINT "hot."
Pete loses before he's even began! He's the worst, so none of the rest of you have any pressure at all to do better than he has!
Posted by: Magdha - 01-30-2026, 10:03 AM - Forum: In-Form
- No Replies
The program uses the following InForm objects:
Form
PictureBox
Label
TRackBar
Checkbox
TextBox
Unzip the file and extract the folder into your PEQB64 directory. In the IDE make sure that you have the Run Option “Save EXE in source folder” checked.
': Program by Fellippe Heitor
': This program uses
': This program uses
': InForm-PE for QB64-PE - v1.5.8 based upon InForm by Fellippe Heitor
': Copyright (c) 2025 QB64 Phoenix Edition Team
': https://github.com/QB64-Phoenix-Edition/InForm-PE
'-----------------------------------------------------------
'Improved fireworks:
' - Particles now leave a trail behind
' - Round explosions (sin/cos been used...)
' - Explosion sound effect.
OPTION _EXPLICIT
TYPE Vector
x AS SINGLE
y AS SINGLE
END TYPE
TYPE Particle
Pos AS Vector
Vel AS Vector
Acc AS Vector
Visible AS _BYTE
Exploded AS _BYTE
ExplosionStep AS _BYTE
ExplosionMax AS _BYTE
Color AS _UNSIGNED LONG
Size AS _BYTE
END TYPE
REDIM SHARED Firework(1 TO 1) AS Particle
REDIM SHARED Boom(1 TO UBOUND(Firework) * 2, 1) AS Particle
DIM SHARED Trail(1 TO 20000) AS Particle
DIM SHARED StartPointLimit AS SINGLE, InitialVel AS SINGLE
DIM SHARED Gravity AS Vector, Pause AS _BYTE, distant AS LONG
': Controls' IDs: ------------------------------------------------------------------
DIM SHARED BabyYoureAFirework AS LONG
DIM SHARED Canvas AS LONG
DIM SHARED MaxFireworksLB AS LONG
DIM SHARED MaxFireworksTrackBar AS LONG
DIM SHARED MaxParticlesLB AS LONG
DIM SHARED MaxParticlesTrackBar AS LONG
DIM SHARED ShowTextCB AS LONG
DIM SHARED YourTextHereTB AS LONG
DIM SHARED HappyNewYearLB AS LONG
': Event procedures: ---------------------------------------------------------------
SUB __UI_BeforeInit
END SUB
SUB __UI_OnLoad
_TITLE "Baby, you're a firework"
StartPointLimit = Control(Canvas).Height / 3
Control(MaxFireworksTrackBar).Value = 20
Control(MaxParticlesTrackBar).Value = 150
ToolTip(MaxFireworksTrackBar) = "20"
ToolTip(MaxParticlesTrackBar) = "150"
REDIM _PRESERVE Firework(1 TO Control(MaxFireworksTrackBar).Value) AS Particle
REDIM _PRESERVE Boom(1 TO UBOUND(Firework) * 2, Control(MaxParticlesTrackBar).Value) AS Particle
END SUB
SUB __UI_BeforeUpdateDisplay
STATIC JustExploded AS _BYTE
STATIC t AS INTEGER, Initial AS _BYTE, InitialX AS INTEGER, lastInitial#
DIM AS LONG j, i, a
DIM AS _UNSIGNED LONG thisColor
BeginDraw Canvas
IF JustExploded THEN
JustExploded = False
CLS , _RGB32(0, 0, 50)
ELSE
CLS
END IF
IF _CEIL(RND * 20) < 2 OR (Initial = False AND TIMER - lastInitial# > .1) THEN
'Create a new particle
FOR j = 1 TO UBOUND(Firework)
IF Firework(j).Visible = False THEN
Firework(j).Vel.y = InitialVel
Firework(j).Vel.x = 3 - _CEIL(RND * 6)
IF Initial = True THEN
Firework(j).Pos.x = _CEIL(RND * Control(Canvas).Width)
ELSE
Firework(j).Pos.x = InitialX * (Control(Canvas).Width / 15)
InitialX = InitialX + 1
lastInitial# = TIMER
IF InitialX > 15 THEN Initial = True
END IF
Firework(j).Pos.y = Control(Canvas).Height + _CEIL(RND * StartPointLimit)
Firework(j).Visible = True
Firework(j).Exploded = False
Firework(j).ExplosionStep = 0
Firework(j).Size = _CEIL(RND * 2)
IF Firework(j).Size = 1 THEN
Firework(j).ExplosionMax = 9 + _CEIL(RND * 41)
ELSE
Firework(j).ExplosionMax = 9 + _CEIL(RND * 71)
END IF
Firework(j).ExplosionMax = 20 '0
EXIT FOR
END IF
NEXT j
END IF
'Show trail
FOR i = 1 TO UBOUND(Trail)
IF NOT Pause THEN Trail(i).Color = Darken(Trail(i).Color, 70)
IF Trail(i).Size = 1 THEN
PSET (Trail(i).Pos.x, Trail(i).Pos.y), Trail(i).Color
ELSE
PSET (Trail(i).Pos.x, Trail(i).Pos.y), Trail(i).Color
PSET (Trail(i).Pos.x - 1, Trail(i).Pos.y), Trail(i).Color
PSET (Trail(i).Pos.x + 1, Trail(i).Pos.y), Trail(i).Color
PSET (Trail(i).Pos.x, Trail(i).Pos.y - 1), Trail(i).Color
PSET (Trail(i).Pos.x, Trail(i).Pos.y + 1), Trail(i).Color
END IF
NEXT i
'Update and show particles
FOR i = 1 TO UBOUND(Firework)
'Update trail particles
IF Firework(i).Visible = True AND Firework(i).Exploded = False AND NOT Pause THEN
t = t + 1: IF t > UBOUND(Trail) THEN t = 1
Trail(t).Pos.x = Firework(i).Pos.x
Trail(t).Pos.y = Firework(i).Pos.y
Trail(t).Color = _RGB32(255, 255, 255)
'New position
Firework(i).Vel.y = Firework(i).Vel.y + Gravity.y
Firework(i).Pos.y = Firework(i).Pos.y + Firework(i).Vel.y
Firework(i).Pos.x = Firework(i).Pos.x + Firework(i).Vel.x
END IF
'Explode the particle if it reaches max height
IF Firework(i).Vel.y > 0 THEN
IF Firework(i).Exploded = False THEN
Firework(i).Exploded = True
JustExploded = True
IF Firework(1).Size = 1 THEN
IF distant THEN _SNDPLAYCOPY distant, .5
ELSE
IF distant THEN _SNDPLAYCOPY distant, 1
END IF
Control(HappyNewYearLB).Hidden = NOT Control(ShowTextCB).Value
EndDraw Canvas
END SUB
SUB __UI_BeforeUnload
END SUB
SUB __UI_Click (id AS LONG)
SELECT CASE id
CASE BabyYoureAFirework
CASE Canvas
Pause = NOT Pause
IF Pause THEN
Caption(HappyNewYearLB) = "PAUSED"
ELSE
Caption(HappyNewYearLB) = Text(YourTextHereTB)
END IF
CASE MaxFireworksLB
CASE MaxFireworksTrackBar
CASE MaxParticlesLB
CASE MaxParticlesTrackBar
CASE ShowTextCB
CASE YourTextHereTB
CASE HappyNewYearLB
END SELECT
END SUB
SUB __UI_MouseEnter (id AS LONG)
SELECT CASE id
CASE BabyYoureAFirework
CASE Canvas
CASE MaxFireworksLB
CASE MaxFireworksTrackBar
CASE MaxParticlesLB
CASE MaxParticlesTrackBar
CASE ShowTextCB
CASE YourTextHereTB
CASE HappyNewYearLB
END SELECT
END SUB
SUB __UI_MouseLeave (id AS LONG)
SELECT CASE id
CASE BabyYoureAFirework
CASE Canvas
CASE MaxFireworksLB
CASE MaxFireworksTrackBar
CASE MaxParticlesLB
CASE MaxParticlesTrackBar
CASE ShowTextCB
CASE YourTextHereTB
CASE HappyNewYearLB
END SELECT
END SUB
SUB __UI_FocusIn (id AS LONG)
SELECT CASE id
CASE MaxFireworksTrackBar
CASE MaxParticlesTrackBar
CASE ShowTextCB
CASE YourTextHereTB
END SELECT
END SUB
SUB __UI_FocusOut (id AS LONG)
SELECT CASE id
CASE MaxFireworksTrackBar
CASE MaxParticlesTrackBar
CASE ShowTextCB
CASE YourTextHereTB
END SELECT
END SUB
SUB __UI_MouseDown (id AS LONG)
SELECT CASE id
CASE BabyYoureAFirework
CASE Canvas
CASE MaxFireworksLB
CASE MaxFireworksTrackBar
CASE MaxParticlesLB
CASE MaxParticlesTrackBar
CASE ShowTextCB
CASE YourTextHereTB
CASE HappyNewYearLB
END SELECT
END SUB
SUB __UI_MouseUp (id AS LONG)
SELECT CASE id
CASE BabyYoureAFirework
CASE Canvas
CASE MaxFireworksLB
CASE MaxFireworksTrackBar
CASE MaxParticlesLB
CASE MaxParticlesTrackBar
CASE ShowTextCB
CASE YourTextHereTB
CASE HappyNewYearLB
END SELECT
END SUB
SUB __UI_KeyPress (id AS LONG)
SELECT CASE id
CASE MaxFireworksTrackBar
CASE MaxParticlesTrackBar
CASE ShowTextCB
CASE YourTextHereTB
END SELECT
END SUB
SUB __UI_TextChanged (id AS LONG)
SELECT CASE id
CASE YourTextHereTB
Caption(HappyNewYearLB) = Text(YourTextHereTB)
END SELECT
END SUB
SUB __UI_ValueChanged (id AS LONG)
Control(id).Value = INT(Control(id).Value)
SELECT CASE id
CASE ShowTextCB
CASE MaxFireworksTrackBar
REDIM _PRESERVE Firework(1 TO Control(MaxFireworksTrackBar).Value) AS Particle
ToolTip(id) = STR$(Control(MaxFireworksTrackBar).Value)
CASE MaxParticlesTrackBar
REDIM _PRESERVE Boom(1 TO UBOUND(Firework) * 2, Control(MaxParticlesTrackBar).Value) AS Particle
ToolTip(id) = STR$(Control(MaxParticlesTrackBar).Value)
END SELECT
END SUB
So far I've managed to avoid using any Win32API calls in my current project. Ah, but now I'm adding a font increase/decrease function and I'm used to using Ctrl + and Ctrl -, in Firefox, to increase and decrease the font size. So I'd like to use the same key combos in QB64, but...
I don't believe GLUT has a way for us to map those combos, so _KeyHit, _Keydown, Inp, Inkey$, etc. don't seem to have a way to make it so. Simply put, when the control key is held down in QB64, the minus and plus keys (along with some others) are not recognized. At least that's been my experience, but maybe I'm missing something here.
With v 4.4.0, QB64PE has finally adopted to the QB45 style of allowing users to put SUBS and FUNCTIONS anywhere inside their code.
This was something which QB45 and QBASIC always allowed, but the old IDE had a habit of moving subs to their own windows (if you guys remember), and then it saved them at the end of the code. You'd have to write code in EDIT or notepad or such, and then compile it from the command line so the IDE wouldn't move those subs/functions automagically on you, but it always has been a part of the language itself.
It's the reason why QB45 allowed you to use a single file for $INCLUDE libraries and not have to split your work into two files to separate into the top and the bottom of the code. Just one file at the top and you're good to go with $INCLUDE!
But there is something that folks need to be aware of, and keep in mind -- BASIC has certain commands and precompiler commands which run in a simple TOP TO BOTTOM format, so you can't think of it as the SUBs just being moved to the end of the code.
For example, let's take a look at the following code:
If you notice, the DEFINT is a TOP TO BOTTOM command. So is the $IF and $LET commands.
Even though we change the DEF *after* the sub, it does nothing to change the default variable types inside the sub.
Same with the $LET. Where the SUB is located in our code, the $LET isn't defined and thus defaults to FALSE. Even though we set the precompiler variable AFTER the sub, it's not going to have any affect upon the SUB itself -- even if we call the SUB afterwards.
DEF statements, _DEFINE statements, $CHECKING, $IF, $LET and other such commands work on a simple precompiler TOP TO BOTTOM format. Keep this in mind and don't think that you can $LET a variable after the SUB and have it affect the sub. It doesn't work like that.
If you're going to create libraries which rely on DEF statements or $IF conditions, the order of which your commands come is very important.
Code: (Select All)
$LET foo = TRUE
$INCLUDE:'My_Lib.BI'
The above would carry the value of *foo* into the library.
Code: (Select All)
$INCLUDE:'My_Lib.BI'
$LET foo = TRUE
Whereas the above here wouldn't. Make certain you guys don't get confused now that SUBS and such might be earlier in the code or in the libraries. Program flow is still as important as ever. It's just that where you place those SUB/FUNCTION statements has now become a lot more flexible than ever before.
I know it wasn't long ago that we brought you guys version 4.3.0, but we've got you a brand new shiny and nifty new little 4.4 to enjoy!!
v4.4.0 Latest Enhancements
#670 - Allow interleaving SUB/FUNCTIONs with main program, fixing #504 - @flukiluke
This renders the error Statement cannot be placed between SUBs and FUNCTIONs obsolete.
It also makes building libraries easier, as everything (CONST, TYPE, DIM, SUB, FUNCTION) can go into the same file now.
Library Updates
#674 - Miniaudio rolled back to the previous version - @RhoSigma-QB64
This is now the same version we had in QB64-PE v4.2.0, as the updated version in v4.3.0 had issues with music streaming when tailing at the same time (writing more data to the stream while it is playing).
Bug Fixes
#665 - Export As... fixes - @RhoSigma-QB64
Now auto-closing strings at line end if required.
Fixed look-ahead logic to avoid partly name matches causing wrong output.
#666 - Restore IDE behavior when Autobrackets is disabled - @SteveMcNeill
With the change to autobrackets in v4.3.0, the IDE suddenly started to just skip over end bracket type symbols if one were to type them and the next character was an end bracket. This makes sense with the autobracket enabled, but when it's turned off, those keystrokes should be processed as before, without skipping that next character.
#671 - Do not over-apply auto-semicolon insertion, fixing #575 - @flukiluke
String literals in a PRINT statement may be subject to automatic semicolon insert on either side. For instance, PRINT "abc"123, 123"abc" is equivalent to PRINT "abc"; 123, 213; "abc". The logic was not accounting for the possibility of a comparison operator before/after a string literal, and so PRINT "abc" = "def" gets turned into the invalid PRINT "abc"; <>; "def". These operators are now checked for.
This is one of those enhancements which STEVE thinks everyone should grab and enjoy as soon as possible!! This does one of the things that I personally think we've been needing for a long time now:
It allows library creators to make libraries with just a single $INCLUDE file in them!!
No longer do you need a *.BI file for the top of your library, and a *.BM file for the bottom of your library. Just one single file is good enough. Place it at the start of your code and you're golden!!
Note that this also allows you to do some odd looking stuff, which I really don't recommend, but which *is* technically possible. Notice the following code:
That's actually 100% QB45-compatible code. It's not something which we supported before, but it WORKS and has worked in QB45 and QBASIC since forever. This is why QB45 had only one single $INCLUDE, while old versions of QB64 had to be separated into the top and bottom portions of your code base.
In most cases, writing code like this will make things harder for the programmer to understand and follow, and I discourage doing it.
For people writing library style code for use with $INCLUDE, however, this is a great change as you can now put your entire library into a single file and share it, $INCLUDE it, work with it, format it, and all that as just a single file.
I imagine a LOT of libraries are going to start adapting to this new single file format before long, as it's just easier in general for most of the creators to keep up with things, maintain them, and share them, as a single file, so if you like to $INCLUDE others stuff in your code, you'll probably want to grab the latest version here and update without skipping this release.
The program uses the following InForm objects:
Form
RadioButton
Button
Label
TextBox
ListBox
CheckBox
Unzip the file and extract the folder into your PEQB64 directory. In the IDE make sure that you have the Run Option “Save EXE in source folder” checked.
' ebacCalculator.bas Version 2.0 10/14/2021
'-----------------------------------------------------------------------------------
' PROGRAM: ebacCalculator.bas
' AUTHOR: George McGinn
'
' DATE WRITTEN: 04/01/2021
' VERSION: 2.0
' PROJECT: Estimated Blood-Alcohol Content Calculator
'
' DESCRIPTION: Program shows many of the functions of using InForm while using
' most of the original code from the Zenity project. This can now
' run on all systems (Linux, MAC and Windows).
'
' Written by George McGinn
' Copyright (C)2021 by George McGinn - All Rights Reserved
' Version 1.0 - Created 04/01/2021
' Version 2.0 - Created 10/14/2021
'
' CHANGE LOG
'-----------------------------------------------------------------------------------
' 04/01/2021 v1.0 GJM - New Program (TechBASIC and C++ Versions).
' 06/19/2021 v1.5 GJM - Updated to use Zenity and SHELL commands to run on Linux with
' a simple GUI.
' 10/14/2021 v2.0 GJM - Updated to use InForm GUI in place of Zenity an SHELL commands.
' Can now run on any OS
'-----------------------------------------------------------------------------------
' Copyright (C)2021 by George McGinn. All Rights Reserved.
'
' untitled.bas by George McGinn is licensed under a Creative Commons
' Attribution-NonCommercial 4.0 International. (CC BY-NC 4.0)
'
' Full License Link: https://creativecommons.org/licenses/by-.../legalcode
'
'-----------------------------------------------------------------------------------
' PROGRAM NOTES
'
': This program uses
': InForm - GUI library for QB64 - v1.3
': Fellippe Heitor, 2016-2021 - fellippe@qb64.org - @fellippeheitor
': https://github.com/FellippeHeitor/InForm
'-----------------------------------------------------------------------------------
': Controls' IDs: ------------------------------------------------------------------
DIM SHARED maleRB AS LONG
DIM SHARED femaleRB AS LONG
DIM SHARED AgreeCB AS LONG
DIM SHARED AGREEBT AS LONG
DIM SHARED ebacFRM AS LONG
DIM SHARED SexLB AS LONG
DIM SHARED weightLB AS LONG
DIM SHARED nbrdrinksLB AS LONG
DIM SHARED timeLB AS LONG
DIM SHARED EnterInformationLB AS LONG
DIM SHARED WeightTB AS LONG
DIM SHARED nbrDrinksTB AS LONG
DIM SHARED TimeTB AS LONG
DIM SHARED CancelBT AS LONG
DIM SHARED OKBT AS LONG
DIM SHARED HELPBT AS LONG
DIM SHARED QUITBT AS LONG
DIM SHARED displayResults AS LONG
DIM SHARED informationLB AS LONG
': User-defined Variables: ---------------------------------------------------------
DIM SHARED AS STRING HELPFile
DIM SHARED AS INTEGER SOBER, legalToDrive
DIM SHARED AS STRING Sex
DIM SHARED AS DOUBLE A, T
DIM SHARED AS SINGLE B, OZ, Wt, EBAC
': Event procedures: ---------------------------------------------------------------
SUB __UI_BeforeInit
END SUB
SUB __UI_OnLoad
' *** Initialize Variables
A = 0
Wt = 0
B = .0
T = 0: St = 0
I = 0
Bdl = 1.055
OZ = .5
SOBER = False: legalToDrive = False
HELPFile = "EBACHelp.txt"
displayDisclaimer
END SUB
SUB __UI_BeforeUpdateDisplay
'This event occurs at approximately 60 frames per second.
'You can change the update frequency by calling SetFrameRate DesiredRate%
END SUB
SUB __UI_BeforeUnload
'If you set __UI_UnloadSignal = False here you can
'cancel the user's request to close.
END SUB
SUB __UI_Click (id AS LONG)
SELECT CASE id
CASE ebacFRM
CASE SexLB
CASE weightLB
CASE nbrdrinksLB
CASE timeLB
CASE EnterInformationLB
CASE WeightTB
CASE nbrDrinksTB
CASE TimeTB
CASE informationLB
CASE displayResults
CASE AgreeCB
CASE maleRB
Sex = "M"
CASE femaleRB
Sex = "F"
CASE AGREEBT
Answer = MessageBox("Do you want to perform another calculation? ", "", MsgBox_YesNo + MsgBox_Question)
IF Answer = MsgBox_Yes THEN
Control(AgreeCB).Value = False
Control(AGREEBT).Disabled = True
ELSE
Answer = MessageBox("Thank You for using EBAC Calculator. Please Don't Drink and Drive.", "", MsgBox_Ok + MsgBox_Information)
SYSTEM
END IF
CASE CancelBT
ResetForm
CASE OKBT
IF Control(maleRB).Value = False AND Control(femaleRB).Value = False THEN
Answer = MessageBox("Invalid: You must select either M (male) or F (female). Please Correct.", "", MsgBox_Ok + MsgBox_Information)
EXIT SUB
END IF
A = Control(nbrDrinksTB).Value
Wt = Control(WeightTB).Value
T = Control(TimeTB).Value
calcEBAC
Control(QUITBT).Disabled = True
ResetList displayResults
Text(displayResults) = prt_text
CASE HELPBT
ResetList displayResults
IF _FILEEXISTS(HELPFile) THEN
DIM fh AS LONG
fh = FREEFILE
OPEN HELPFile FOR INPUT AS #fh
DO UNTIL EOF(fh)
LINE INPUT #fh, helpcontents
AddItem displayResults, helpcontents
LOOP
CLOSE #fh
Control(displayResults).LastVisibleItem = 0
ELSE
Answer = MessageBox("HELP File " + HELPFile$ + " Not Found ", "", MsgBox_Ok + MsgBox_Question)
SYSTEM 1
END IF
CASE QUITBT
Answer = MessageBox("Are you sure you want to QUIT? ", "", MsgBox_YesNo + MsgBox_Question)
IF Answer = MsgBox_Yes THEN
Answer = MessageBox("Thank You for using EBAC Calculator. Please Don't Drink and Drive.", "", MsgBox_Ok + MsgBox_Information)
SYSTEM
END IF
END SELECT
END SUB
SUB __UI_MouseEnter (id AS LONG)
SELECT CASE id
CASE ELSE
END SELECT
END SUB
SUB __UI_MouseLeave (id AS LONG)
SELECT CASE id
CASE ELSE
END SELECT
END SUB
SUB __UI_FocusIn (id AS LONG)
SELECT CASE id
CASE ELSE
END SELECT
END SUB
SUB __UI_FocusOut (id AS LONG)
'This event occurs right before a control loses focus.
'To prevent a control from losing focus, set __UI_KeepFocus = True below.
SELECT CASE id
CASE ELSE
END SELECT
END SUB
SUB __UI_MouseDown (id AS LONG)
SELECT CASE id
CASE ELSE
END SELECT
END SUB
SUB __UI_MouseUp (id AS LONG)
SELECT CASE id
CASE ELSE
END SELECT
END SUB
SUB __UI_KeyPress (id AS LONG)
'When this event is fired, __UI_KeyHit will contain the code of the key hit.
'You can change it and even cancel it by making it = 0
SELECT CASE id
CASE ELSE
END SELECT
END SUB
SUB __UI_TextChanged (id AS LONG)
SELECT CASE id
CASE WeightTB
Control(AgreeCB).Value = False
Control(AGREEBT).Disabled = True
CASE nbrDrinksTB
Control(AgreeCB).Value = False
Control(AGREEBT).Disabled = True
CASE TimeTB
Control(AgreeCB).Value = False
Control(AGREEBT).Disabled = True
END SELECT
END SUB
SUB __UI_ValueChanged (id AS LONG)
SELECT CASE id
CASE displayResults
CASE maleRB
Control(AgreeCB).Value = False
Control(AGREEBT).Disabled = True
CASE femaleRB
Control(AgreeCB).Value = False
Control(AGREEBT).Disabled = True
CASE AgreeCB
IF Control(AgreeCB).Value = True THEN
Control(AGREEBT).Disabled = False
Control(QUITBT).Disabled = False
ELSE
Control(AGREEBT).Disabled = True
Control(QUITBT).Disabled = True
END IF
END SELECT
END SUB
SUB __UI_FormResized
END SUB
': User FUNCTIONS/SUBROUTINES: ---------------------------------------------------------------
SUB displayDisclaimer
' prt_text = "*** DISCLAIMER ***" + CHR$(10)
prt_text = "Unless otherwise separately undertaken by the Licensor, to the extent" + CHR$(10)
prt_text = prt_text + "possible, the Licensor offers the Licensed Material as-is and" + CHR$(10)
prt_text = prt_text + "as-available, and makes no representations or warranties of any kind" + CHR$(10)
prt_text = prt_text + "concerning the Licensed Material, whether express, implied, statutory," + CHR$(10)
prt_text = prt_text + "or other. This includes, without limitation, warranties of title," + CHR$(10)
prt_text = prt_text + "merchantability, fitness for a particular purpose, non-infringement," + CHR$(10)
prt_text = prt_text + "absence of latent or other defects, accuracy, or the presence or absence" + CHR$(10)
prt_text = prt_text + "of errors, whether or not known or discoverable. Where disclaimers of" + CHR$(10)
prt_text = prt_text + "warranties are not allowed in full or in part, this disclaimer may not" + CHR$(10)
prt_text = prt_text + "apply to You." + CHR$(10) + CHR$(10)
prt_text = prt_text + "To the extent possible, in no event will the Licensor be liable to You" + CHR$(10)
prt_text = prt_text + "on any legal theory (including, without limitation, negligence) or" + CHR$(10)
prt_text = prt_text + "otherwise for any direct, special, indirect, incidental, consequential," + CHR$(10)
prt_text = prt_text + "punitive, exemplary, or other losses, costs, expenses, or damages" + CHR$(10)
prt_text = prt_text + "arising out of this Public License or use of the Licensed Material, even" + CHR$(10)
prt_text = prt_text + "if the Licensor has been advised of the possibility of such losses," + CHR$(10)
prt_text = prt_text + "costs, expenses, or damages. Where a limitation of liability is not" + CHR$(10)
prt_text = prt_text + "allowed in full or in part, this limitation may not apply to You." + CHR$(10) + CHR$(10)
prt_text = prt_text + "The disclaimer of warranties and limitation of liability provided above" + CHR$(10)
prt_text = prt_text + "shall be interpreted in a manner that, to the extent possible, most" + CHR$(10)
prt_text = prt_text + "closely approximates an absolute disclaimer and waiver of all liability." + CHR$(10)
Answer = MessageBox(prt_text, "*** DISCLAIMER ***", MsgBox_YesNo + MsgBox_Question)
IF Answer = MsgBox_No THEN
Answer = MessageBox("Sorry you don't agree. Please Don't Drink and Drive.", "", MsgBox_Ok + MsgBox_Information)
SYSTEM
END IF
END SUB
SUB ResetForm
Control(nbrDrinksTB).Value = 0
Control(WeightTB).Value = 0
Control(TimeTB).Value = 0
Control(AgreeCB).Value = False
Control(AGREEBT).Disabled = True
Control(maleRB).Value = False
Control(femaleRB).Value = False
ResetList displayResults
Sex = ""
END SUB
SUB calcEBAC
'-------------------------------------------------------------
' *** Convert Drinks into Fluid Ounces of EtOH (Pure Alcohol).
' *** A is number of drinks. 1 drink is about .6 FLoz of alcohol
FLoz = A * OZ
legalToDrive = False
'-----------------------------------------------------
' *** Set/calculate EBAC values based on Sex
SELECT CASE Sex
CASE "M"
B = .017
EBAC = 7.97 * FLoz / Wt - B * T
CASE "F"
B = .019
EBAC = 9.86 * FLoz / Wt - B * T
END SELECT
IF EBAC < 0 THEN EBAC = 0
'----------------------------------------------------------------------------------------------
' *** Populate the EBAC string with the EBAC value formatted to 3 decimal places for FORM output
prt_text = "ESTIMATED BLOOD ALCOHOL CONTENT (EBAC) in g/dL = " + strFormat$(STR$(EBAC), "###.###") + CHR$(10) + CHR$(10)
'-----------------------------------------------------------------------------------------
' *** Based on EBAC range values, populate the FORM output string with the approriate text
SELECT CASE EBAC
CASE .500 TO 100.9999
prt_text = prt_text + "*** ALERT: CALL AN AMBULANCE, DEATH LIKELY" + CHR$(10)
prt_text = prt_text + "Unconsious/coma, unresponsive, high likelihood of death. It is illegal" + CHR$(10) + _
"to operate a motor vehicle at this level of intoxication in all states." + CHR$(10)
CASE .400 TO .4999
prt_text = prt_text + "*** ALERT: CALL AN AMBULANCE, DEATH POSSIBLE" + CHR$(10)
prt_text = prt_text + "Onset of coma, and possible death due to respiratory arrest. It is illegal" + CHR$(10) + _
"to operate a motor vehicle at this level of intoxication in all states." + CHR$(10)
CASE .350 TO .3999
prt_text = prt_text + "*** ALERT: CALL AN AMBULANCE, SEVERE ALCOHOL POISONING" + CHR$(10)
prt_text = prt_text + " Coma is possible. This is the level of surgical anesthesia. It is illegal" + CHR$(10) + _
"to operate a motor vehicle at this level of intoxication in all states." + CHR$(10)
CASE .300 TO .3499
prt_text = prt_text + "*** ALERT: YOU ARE IN A DRUNKEN STUP0R, AT RISK TO PASSING OUT" + CHR$(10)
prt_text = prt_text + "STUPOR. You have little comprehension of where you are. You may pass out" + CHR$(10) + _
"suddenly and be difficult to awaken. It is illegal to operate a motor" + CHR$(10) + _
"vehicle at this level of intoxication in all states." + CHR$(10)
CASE .250 TO .2999
prt_text = prt_text + "*** ALERT: SEVERLY IMPAIRED - DRUNK ENOUGH TO CAUSE SEVERE INJURY/DEATH TO SELF" + CHR$(10)
prt_text = prt_text + "All mental, physical and sensory functions are severely impaired." + CHR$(10) + _
"Increased risk of asphyxiation from choking on vomit and of seriously injuring" + CHR$(10) + _
"yourself by falls or other accidents. It is illegal to operate a motor" + CHR$(10) + _
"vehicle at this level of intoxication in all states." + CHR$(10)
CASE .200 TO .2499
prt_text = prt_text + "YOU ARE EXTREMELY DRUNK" + CHR$(10)
prt_text = prt_text + "Feeling dazed/confused or otherwise disoriented. May need help to" + CHR$(10) + _
"stand/walk. If you injure yourself you may not feel the pain. Some" + CHR$(10) + _
"people have nausea and vomiting at this level. The gag reflex" + CHR$(10) + _
"is impaired and you can choke if you do vomit. Blackouts are likely" + CHR$(10) + _
"at this level so you may not remember what has happened. It is illegal" + CHR$(10) + _
"to operate a motor vehicle at this level of intoxication in all states." + CHR$(10)
CASE .160 TO .1999
prt_text = prt_text + "YOUR ARE SEVERLY DRUNK - ENOUGH TO BECOME VERY SICK" + CHR$(10)
prt_text = prt_text + "Dysphoria* predominates, nausea may appear. The drinker has the appearance" + CHR$(10) + _
"of a 'sloppy drunk.' It is illegal to operate a motor vehicle at this level" + CHR$(10) + _
"of intoxication in all states." + CHR$(10) + CHR$(10) + _
"* Dysphoria: An emotional state of anxiety, depression, or unease." + CHR$(10)
CASE .130 TO .1599
prt_text = prt_text + "YOU ARE VERY DRUNK - ENOUGH TO LOSE PHYSICAL & MENTAL CONTROL" + CHR$(10)
prt_text = prt_text + "Gross motor impairment and lack of physical control. Blurred vision and major" + CHR$(10) + _
"loss of balance. Euphoria is reduced and dysphoria* is beginning to appear." + CHR$(10) + _
"Judgment and perception are severely impaired. It is illegal to operate a " + CHR$(10) + _
"motor vehicle at this level of intoxication in all states." + CHR$(10) + CHR$(10)
prt_text = prt_text + "* Dysphoria: An emotional state of anxiety, depression, or unease." + CHR$(10)
CASE .100 TO .1299
prt_text = prt_text + "YOU ARE LEGALLY DRUNK" + CHR$(10)
prt_text = prt_text + "Significant impairment of motor coordination and loss of good judgment." + CHR$(10) + _
"Speech may be slurred; balance, vision, reaction time and hearing will be" + CHR$(10) + _
"impaired. Euphoria. It is illegal to operate a motor vehicle at this level" + CHR$(10) + _
"of intoxication in all states." + CHR$(10)
CASE .070 TO .0999
prt_text = prt_text + "YOU MAY BE LEGALLY DRUNK" + CHR$(10)
prt_text = prt_text + "Slight impairment of balance, speech, vision, reaction time, and hearing." + CHR$(10) + _
"Euphoria. Judgment and self-control are reduced, and caution, reason and" + CHR$(10) + _
"memory are impaired (in some* states .08 is legally impaired and it is illegal" + CHR$(10) + _
"to drive at this level). You will probably believe that you are functioning" + CHR$(10) + _
"better than you really are." + CHR$(10) + CHR$(10)
prt_text = prt_text + "(*** As of July, 2004 ALL states had passed .08 BAC Per Se Laws. The final" + CHR$(10) + _
"one took effect in August of 2005.)" + CHR$(10)
CASE .040 TO .0699
prt_text = prt_text + "YOU MAY BE LEGALLY BUZZED" + CHR$(10)
prt_text = prt_text + "Feeling of well-being, relaxation, lower inhibitions, sensation of warmth." + CHR$(10) + _
"Euphoria. Some minor impairment of reasoning and memory, lowering of caution." + CHR$(10) + _
"Your behavior may become exaggerated and emotions intensified (Good emotions" + CHR$(10) + _
"are better, bad emotions are worse)" + CHR$(10)
CASE .020 TO .0399
prt_text = prt_text + "YOU MAY BE OK TO DRIVE, BUT IMPAIRMENT BEGINS" + CHR$(10)
prt_text = prt_text + "No loss of coordination, slight euphoria and loss of shyness. Depressant effects" + CHR$(10) + _
"are not apparent. Mildly relaxed and maybe a little lightheaded." + CHR$(10)
CASE .000 TO .0199
prt_text = prt_text + "YOU ARE OK TO DRIVE" + CHR$(10)
END SELECT
'-----------------------------------------------------------
'*** Determine if Drunk (>.08 EBAC) and calculate:
'*** - When user will be less than .08
'*** - How long it will take to become completely sober
IF EBAC > .08 THEN
SOBER = False
CEBAC = EBAC
st = T
DO UNTIL SOBER = True
T = T + 1
IF CEBAC > .0799 THEN I = I + 1
SELECT CASE Sex
CASE "M"
B = .017
CEBAC = 7.97 * FLoz / Wt - B * T
CASE "F"
B = .019
CEBAC = 9.86 * FLoz / Wt - B * T
END SELECT
IF legalToDrive = False THEN
IF CEBAC < .08 THEN
prt_text = prt_text + CHR$(10) + CHR$(10) + "It will take about " + strFormat$(STR$(I), "##") + " hours from your last drink to be able to drive." + CHR$(10)
legalToDrive = True
END IF
END IF
IF CEBAC <= 0 THEN
prt_text = prt_text + "It will take about " + strFormat$(STR$(T - st), "##") + " hours from your last drink to be completely sober."
SOBER = True
END IF
LOOP
END IF
END SUB
FUNCTION strFormat$ (text AS STRING, template AS STRING)
'-----------------------------------------------------------------------------
' *** Return a formatted string to a variable
'
d = _DEST: s = _SOURCE
n = _NEWIMAGE(80, 80, 0)
_DEST n: _SOURCE n
PRINT USING template; VAL(text)
FOR i = 1 TO 79
t$ = t$ + CHR$(SCREEN(1, i))
NEXT
IF LEFT$(t$, 1) = "%" THEN t$ = MID$(t$, 2)
strFormat$ = _TRIM$(t$)
_DEST d: _SOURCE s
_FREEIMAGE n
END FUNCTION
I was hoping to use _MAPTRIANGLE and create a form of 3D text which might end up looking nice and being all rotatable and tiltable and scalable and such.
You can see where I was hoping to add a depth to my text and have maptriangle make it more of a 3d text, but I haven't got there yet.
BUT at this point I *can* rotate on the X/Y/Z axis with my text! And I can scale the text!
I just need to sorts out how to make my 2D text look more 3D, and at the moment I'm thunking my head against the keyboard and not getting the effect I was looking for. Peter, Unseen, MasterGy.... You guys are much better at this 3D stuff than I am. Any ideas on how to get that depth working with my text here?
Feel free to modify, expand, blow up this code as you will. I'll keep pondering and working with it, but hopefully one of you guys will have some insight into this. I remember ages ago that Galleon did a demo of mapping a 2D sprite of mario in a car and making it look pseduo-3d. Does anyone have that old demo saved anywhere? It seems to almost be what I'm looking to do here and might be a good place to start on getting to it do that final 3d enhancement.
I've been working on filling in some of the missing tools in my draw library and one thing I noticed that I was missing was a really good filled arc routine. QB64's method was to draw a circle, choose a point inside the arc, and then use PAINT to fill that circle....
and it didn't work as it never seemed to properly close the arcs and then the paint bled out and colored the entire screen.
The CircleFill that I shared and everyone has used for ages and ages works off LINE statements and thus can never bleed outside the circle itself. It's fast and lovely and nice.
And so, I thought, can I come up with a similar way to do ARCs?
After a few different tries, this is what I've came up with -- a scanline polygon fill routine for arcs.
' ---- Outer arc (A1 ? A2) ---- For angle = a1 To a2 Step angStep
x = cx + r2 * Cos(_D2R(angle)): y = cy - r2 * Sin(_D2R(angle)): vx(n) = x: vy(n) = y: n = n + 1 Next
' Ensure exact endpoint
x = cx + r2 * Cos(_D2R(a2)): y = cy - r2 * Sin(_D2R(a2)): vx(n) = x: vy(n) = y: n = n + 1
' ---- Inner arc (A2 ? A1, reversed) ---- For angle = a2 To a1 Step -angStep
x = cx + r1 * Cos(_D2R(angle)): y = cy - r1 * Sin(_D2R(angle)): vx(n) = x: vy(n) = y: n = n + 1 Next
' Ensure exact endpoint
x = cx + r1 * Cos(_D2R(a1)): y = cy - r1 * Sin(_D2R(a1)): vx(n) = x: vy(n) = y: n = n + 1
' ---- Scanline fill ----
minY = vy(0): maxY = vy(0) For i = 1To n - 1: maxY = _Max(maxY, vy(i)): minY = _Min(minY, vy(i)): Next
For yScan = minY To maxY
count = 0 ' Find intersections For i = 0To n - 1
j = (i + 1) Mod n: x1 = vx(i): y1 = vy(i): x2 = vx(j): y2 = vy(j) If (y1 <= yScan And y2 > yScan) Or (y2 <= yScan And y1 > yScan) Then If y2 <> y1 Then interX(count) = x1 + (yScan - y1) * (x2 - x1) / (y2 - y1): count = count + 1 End If Next ' Sort intersections For i = 0To count - 2 For j = i + 1To count - 1 If interX(j) < interX(i) ThenSwap interX(i), interX(j) Next j, i ' Draw spans For i = 0To count - 2Step2 Line (CLng(interX(i)), yScan)-(CLng(interX(i + 1)), yScan), col, BF Next Next yScan End Sub
SubFilledArc (cx AsLong, cy AsLong, r AsLong, a1 AsSingle, a2 AsSingle, col As_UnsignedLong) $Checking:Off ' Filled arc (pie slice) using only LINE (scanline polygon fill) ' cx, cy = center of circle ' r = radius ' a1, a2 = start and end angles in degrees (0 = right, 90 = up) ' col = color
Const angStep! = 1!' angle step in degrees (smaller = smoother arc) DimAsSingle vx(0To720), vy(0To720), angle, x, y DimAsSingle interX(0To720), x1, y1, x2, y2 DimAsLong i, n, count, yScan, j, k, temp, minY, maxY
' Build polygon: start at center
vx(0) = cx: vy(0) = cy: n = 1
' Arc edge points For angle = a1 To a2 Step angStep 'with a higher anglestep we have a less rounded arc and more of a polygon figure
x = cx + r * Cos(_D2R(angle)): y = cy - r * Sin(_D2R(angle))
vx(n) = x: vy(n) = y: n = n + 1 Next angle
' Ensure last point exactly at a2
x = cx + r * Cos(_D2R(a2)): y = cy - r * Sin(_D2R(a2))
vx(n) = x: vy(n) = y: n = n + 1
' Close polygon back to center
vx(n) = cx: vy(n) = cy: n = n + 1
' --- Scanline fill of polygon ---
minY = vy(0): maxY = vy(0) For i = 1To n - 1: maxY = _Max(maxY, vy(i)): minY = _Min(minY, vy(i)): Next
For yScan = minY To maxY ' Find intersections of scanline with each edge
count = 0 For i = 0To n - 1
j = (i + 1) Mod n
x1 = vx(i): y1 = vy(i): x2 = vx(j): y2 = vy(j) ' Check if edge crosses this scanline If (y1 <= yScan And y2 > yScan) Or (y2 <= yScan And y1 > yScan) Then If y2 <> y1 Then interX(count) = x1 + (yScan - y1) * (x2 - x1) / (y2 - y1): count = count + 1 End If Next ' Sort intersections (simple bubble sort; count is small) For i = 0To count - 2 For j = i + 1To count - 1 If interX(j) < interX(i) ThenSwap interX(i), interX(j) Next j, i
' Draw horizontal spans between pairs of intersections For i = 0To count - 2Step2 Line ((interX(i)), yScan)-((interX(i + 1)), yScan), col, BF Next Next yScan $Checking:On End Sub
' Filled elliptical arc (pie slice) using only LINE ' cx, cy = center ' a, b = ellipse radii (horizontal, vertical) ' a1, a2 = start/end angles in degrees ' col = color
Const angStep! = 1!' angle step in degrees (smaller = smoother arc) DimAsSingle vx(0To2000), vy(0To2000), angle, x, y DimAsSingle interX(0To2000), x1, y1, x2, y2 DimAsLong i, n, count, yScan, j, k, temp, minY, maxY
' Build polygon: start at center
vx(0) = cx: vy(0) = cy: n = 1
' Arc edge points For angle = a1 To a2 Step angStep 'with a higher anglestep we have a less rounded arc and more of a polygon figure
x = cx + a * Cos(_D2R(angle)): y = cy - b * Sin(_D2R(angle))
vx(n) = x: vy(n) = y: n = n + 1 Next angle
' Ensure last point exactly at a2
x = cx + a * Cos(_D2R(a2)): y = cy - b * Sin(_D2R(a2))
vx(n) = x: vy(n) = y: n = n + 1
' Close polygon back to center
vx(n) = cx: vy(n) = cy: n = n + 1
' --- Scanline fill of polygon ---
minY = vy(0): maxY = vy(0) For i = 1To n - 1: maxY = _Max(maxY, vy(i)): minY = _Min(minY, vy(i)): Next
For yScan = minY To maxY ' Find intersections of scanline with each edge
count = 0 For i = 0To n - 1
j = (i + 1) Mod n
x1 = vx(i): y1 = vy(i): x2 = vx(j): y2 = vy(j) ' Check if edge crosses this scanline If (y1 <= yScan And y2 > yScan) Or (y2 <= yScan And y1 > yScan) Then If y2 <> y1 Then interX(count) = x1 + (yScan - y1) * (x2 - x1) / (y2 - y1): count = count + 1 End If Next ' Sort intersections (simple bubble sort; count is small) For i = 0To count - 2 For j = i + 1To count - 1 If interX(j) < interX(i) ThenSwap interX(i), interX(j) Next j, i
' Draw horizontal spans between pairs of intersections For i = 0To count - 2Step2 Line ((interX(i)), yScan)-((interX(i + 1)), yScan), col, BF Next Next yScan $Checking:On End Sub
' Filled elliptical ring arc using only LINE ' cx, cy = center ' a1, b1 = inner ellipse radii ' a2, b2 = outer ellipse radii ' ang1, ang2 = start/end angles ' col = fill color
For i = 0To n - 1
j = (i + 1) Mod n
x1 = vx(i): y1 = vy(i)
x2 = vx(j): y2 = vy(j)
If (y1 <= yScan And y2 > yScan) Or (y2 <= yScan And y1 > yScan) Then If y2 <> y1 Then
interX(count) = x1 + (yScan - y1) * (x2 - x1) / (y2 - y1)
count = count + 1 End If End If Next
' Sort intersections For i = 0To count - 2 For j = i + 1To count - 1 If interX(j) < interX(i) ThenSwap interX(i), interX(j) Next j, i
' Draw spans For i = 0To count - 2Step2 Line (CLng(interX(i)), yScan)-(CLng(interX(i + 1)), yScan), col, BF Next
It does all the math first and calculates all those points which make up the edges of the circle and the lines of the sides of the arc, and then it draws the lines which makes up the circle.
This seems to work for me, and it seems to work nice and fast enough as to be usable in any program I might have.
You math guru's make take a look at this, go over it with a fine tooth comb like everyone did the CircleFill routine and see what might be improved with it, and share your thoughts with us, if you would. Without a whole lot of other versions to compare against, I don't know whether to call this really speedy or not.
At the moment, it seems suitable for my personal usage. I dunno if others have better or faster versions which do the same thing. If so, please share and let's compare!
(Note that you can really see the polygon code in action if you change the CONST inside the routine. Heck, there might even be times where you WANT to do so, for some particular reason or the other. Change it to something like 10! or 20! and then see how our circle looks. It'll be obvious with those values as to what it's doing for us. (You can also make the routine *faster* if desired by going with a rougher circle. Change the value to 2 or 5 and it's not that visually different, but it changes our polygon count by that step.)
CircleFill is nice and fast. This is my attempt to see if I can do the same style fastness for an ArcFill. Let me know what you guys think about it.
I'll leave the original up here so folks can just grab whatever they need from it with each routine holding independent, but I honestly recommend just using the one routine to do it all. It's all you honestly need now.
Every so often, Steve likes to just play around and have fun and goof off and see just what oddness he can hack into QB64 from time to time.
This time, I decided that what QB64PE *really, really, REALLY* needs is its own mascot helper -- and who better than.... STEVE??
And so, I sat down and decided to create the latest and greatest and newest and most AWESOMEST version of QB64PE ever!!
BEHOLD!!
I give you all...
QB64PE -- with AI STEVE!!!!
Kids, don't try this at home! This is the most high powered, redneck hacked, OMG WHAT DID I JUST SEE, version of QB64 to ever exist. I can just hear @vince falling out of his chair right now. I just hope he can call 911 before he passes away from the awesomeness that this version brings to us.
The archive below is a fully stand-alone WINDOWS version of QB64PE-AI Steve. It even has a compiled EXE in it for all you guys to play around with and test out and enjoy!
As for Linux/Mac folks.... umm.... you *might* grab the source file and compile it on your systems. I dunno exactly how deep the changes go and what all would be required to compile in Linux. Just point your existing version to the "QB64PE-AI Steve.bas" file in the /source folder here, and then try to compile it and see what blows up. You may have to move the created EXE to the proper folder afterwards so it finds the internal files and such in the spot where it expects them.
I really don't know what would be required to get this amazing little program to run on Linux. Probably toss it in a 64-bit WINE version, if one exists? If not, and you don't feel like experimenting, just skip this and shrug it off as "Crazy Ass Steve is just fooling around again!"
Same for anyone on limited bandwidth. Don't bother wasting time to grab this, if you're on a limited bandwidth. This is hacked together, and just me having some fun, with more of a Proof-of-Concept style coding going on here, so I might try some other more complicated stuff later someday with QB64PE. I know it's buggy and has several little glitches that need addressing, but it's just FUN to mess around with and play with, and if you have the bandwidth and are on Windows, and want to waste a few minutes, feel free to check out my new...