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

Username/Email:
  

Password
  





Search Forums

(Advanced Search)

Forum Statistics
» Members: 714
» Latest member: HenryG
» Forum threads: 3,569
» Forum posts: 31,902

Full Statistics

Latest Threads
QB64PE v 4.4.0
Forum: Announcements
Last Post: Unseen Machine
3 hours ago
» Replies: 7
» Views: 626
QBJS v0.10.0 - Release
Forum: QBJS, BAM, and Other BASICs
Last Post: Unseen Machine
3 hours ago
» Replies: 13
» Views: 1,258
Accretion Disk
Forum: Programs
Last Post: Unseen Machine
4 hours ago
» Replies: 10
» Views: 170
Arrays inside Types?
Forum: General Discussion
Last Post: hsiangch_ong
4 hours ago
» Replies: 47
» Views: 1,284
Container Data Structure
Forum: Utilities
Last Post: aadityap0901
Yesterday, 04:50 PM
» Replies: 0
» Views: 43
4x4 Square Elimination Pu...
Forum: bplus
Last Post: bplus
Yesterday, 12:52 PM
» Replies: 11
» Views: 378
Has anybody experience wi...
Forum: Help Me!
Last Post: Rudy M
Yesterday, 08:47 AM
» Replies: 31
» Views: 1,848
Sorting numbers - FiliSor...
Forum: Utilities
Last Post: PhilOfPerth
03-11-2026, 12:48 AM
» Replies: 11
» Views: 281
Quick Sort for variable l...
Forum: Utilities
Last Post: SMcNeill
03-10-2026, 03:14 PM
» Replies: 3
» Views: 81
Ready for Easter!
Forum: Holiday Code
Last Post: bplus
03-10-2026, 12:15 PM
» Replies: 0
» Views: 47

 
  Container Data Structure
Posted by: aadityap0901 - Yesterday, 04:50 PM - Forum: Utilities - No Replies

Behold...
A new data structure for qb64 - The Container
It has many advantages:
* very fast add and delete
* very fast access

Keep in mind that this is not an indexed list, which means:
It shouldn't be used to for search, though it can be used to maintain counts...
It is actually used in Entity Component Systems, and not in many other simple things.

But this can be used in AI projects, to maintain a huge amount of entities.
It is actually configured for 4 byte Unsigned Long values, but it can be configured for fixed length strings.

Code: (Select All)
$Console:Only
Const Total = 16777216 ' 12 seconds on my arm64 laptop
ST! = Timer(0.01)
C$ = ContainerNewCapacity$(16777216)
Print "Container built in"; Timer(0.01) - ST!; "seconds"
Print "Length of C$: "; PrintSize(Len(C$))
For I = 1 To Total
    B~& = _RGBA32(Int(Rnd * 256), Int(Rnd * 256), Int(Rnd * 256), Int(Rnd * 256))
    ContainerAdd C$, B~&
Next I
Print Timer(0.01) - ST!
Sleep
System
Function ContainerNew$ () Static
    Static I As _Unsigned Long
    Static __S$
    __S$ = String$(64, 0): For I = 1 To 64 Step 4: Mid$(__S$, I, 4) = MKL$(_ShR(I, 2)): Next I
    ContainerNew$ = Chr$(16) + MKL$(16) + MKL$(0) + __S$ + __S$ + String$(64, 0)
    ' Signature + Capacity + Length + Index + Index Inverse + Data
End Function
Function ContainerNewCapacity$ (Capacity As _Unsigned Long) Static
    Static As _Unsigned Long I, J
    Static __S$
    __S$ = String$(_ShL(Capacity, 2), 0)
    For I = 1 To _ShL(Capacity, 2) Step 4
        J = _ShR(I, 2)
        Asc(__S$, I) = _Blue32(J)
        Asc(__S$, I + 1) = _Green32(J)
        Asc(__S$, I + 2) = _Red32(J)
        Asc(__S$, I + 3) = _Alpha32(J)
    Next I
    ContainerNewCapacity$ = Chr$(16) + MKL$(Capacity) + MKL$(0) + __S$ + __S$ + String$(_ShL(Capacity, 2), 0)
End Function
Sub ContainerAdd (Container As String, Element As _Unsigned Long) Static
    Static Length~&, Capacity~&
    If Len(Container) < 9 _OrElse Asc(Container) <> 16 Then Container = ContainerNew$
    Capacity~& = _RGBA32(Asc(Container, 4), Asc(Container, 3), Asc(Container, 2), Asc(Container, 5))
    Length~& = _RGBA32(Asc(Container, 8), Asc(Container, 7), Asc(Container, 6), Asc(Container, 9))
    If Capacity~& <= Length~& Then ContainerResizeCapacity Container, _IIf(Capacity~& < 1048576, _ShL(Capacity~&, 1), Capacity~& + 1048576)
    Capacity~& = _RGBA32(Asc(Container, 4), Asc(Container, 3), Asc(Container, 2), Asc(Container, 5))
    Mid$(Container, 6, 4) = MKL$(Length~& + 1)
    Asc(Container, 10 + _ShL(Capacity~&, 3) + _ShL(Length~&, 2)) = _Blue32(Element)
    Asc(Container, 11 + _ShL(Capacity~&, 3) + _ShL(Length~&, 2)) = _Green32(Element)
    Asc(Container, 12 + _ShL(Capacity~&, 3) + _ShL(Length~&, 2)) = _Red32(Element)
    Asc(Container, 9 + _ShL(Capacity~&, 3) + _ShL(Length~&, 2)) = _Alpha32(Element)
End Sub
Function ContainerLength~& (Container As String) Static
    ContainerLength~& = _RGBA32(Asc(Container, 8), Asc(Container, 7), Asc(Container, 6), Asc(Container, 9))
End Function
Function ContainerCapacity~& (Container As String) Static
    ContainerCapacity~& = _RGBA32(Asc(Container, 4), Asc(Container, 3), Asc(Container, 2), Asc(Container, 5))
End Function
Sub ContainerResizeCapacity (Container As String, NewSize As _Unsigned Long) Static
    Static Length~&, Capacity~&
    Capacity~& = _RGBA32(Asc(Container, 4), Asc(Container, 3), Asc(Container, 2), Asc(Container, 5))
    Length~& = _RGBA32(Asc(Container, 8), Asc(Container, 7), Asc(Container, 6), Asc(Container, 9))
    __T~& = _ShL(Capacity~&, 2)
    __Index$ = Mid$(Container, 10, __T~&)
    __IndexInverse$ = Mid$(Container, 10 + __T~&, __T~&)
    __Data$ = Mid$(Container, 10 + _ShL(__T~&, 1), _ShL(__T~&, 2))
    NewSize = _Max(Length~&, NewSize)
    __T~& = NewSize - Capacity~&
    If NewSize > Capacity~& Then
        __Index$ = __Index$ + String$(_ShL(__T~&, 2), 0)
        __IndexInverse$ = __IndexInverse$ + String$(_ShL(__T~&, 2), 0)
        __Data$ = __Data$ + String$(_ShL(__T~&, 2), 0)
    Else
        __Index$ = Left$(__Index$, _ShL(NewSize, 2))
        __IndexInverse$ = Left$(__IndexInverse$, _ShL(NewSize, 2))
        __Data$ = Left$(__Data$, _ShL(Length~&, 2))
    End If
    For I = 1 + _ShL(Capacity~&, 2) To _ShL(NewSize, 2) Step 4
        Mid$(__Index$, I, 4) = MKL$(_ShR(I, 2))
        Mid$(__IndexInverse$, I, 4) = MKL$(_ShR(I, 2))
    Next I
    Container = Chr$(16) + MKL$(NewSize) + MKL$(Length~&) + __Index$ + __IndexInverse$ + __Data$
End Sub
Function ContainerGet~& (Container As String, Index As _Unsigned Long) Static
    Static Length~&, Capacity~&, __Index~&
    Capacity~& = _RGBA32(Asc(Container, 4), Asc(Container, 3), Asc(Container, 2), Asc(Container, 5))
    Length~& = _RGBA32(Asc(Container, 8), Asc(Container, 7), Asc(Container, 6), Asc(Container, 9))
    __T~& = _ShL(Index, 2)
    __Index~& = _RGBA32(Asc(Container, 12 + __T~&), Asc(Container, 11 + __T~&), Asc(Container, 10 + __T~&), Asc(Container, 13 + __T~&))
    __Index~& = 10 + _ShL(__Index~&, 2) + _ShL(Capacity~&, 3)
    ContainerGet~& = _RGBA32(Asc(Container, 2 + __Index~&), Asc(Container, 1 + __Index~&), Asc(Container, __Index~&), Asc(Container, 3 + __Index~&))
End Function
Function PrintSize$ (__T As _Unsigned Long)
    If __T = 0 Then
        PrintSize$ = "0 B"
        Exit Function
    End If
    Select Case Int(Log(__T) / Log(2) / 10)
        Case 0: PrintSize$ = _ToStr$(__T) + " B"
        Case 1: PrintSize$ = _ToStr$(Round(__T / _ShL(1, 10))) + " KB"
        Case 2: PrintSize$ = _ToStr$(Round(__T / _ShL(1, 20))) + " MB"
        Case 3: PrintSize$ = _ToStr$(Round(__T / _ShL(1, 30))) + " GB"
    End Select
End Function
Function Round! (__N As Double)
    Round! = Int(100 * __N) / 100
End Function
This is currently incomplete, but has the most basic functions: add, resize, get
I will add a function to delete later.
I would like to see if anyone else has a data structure like this.
BTW, this is actually made because I saw this:  The magic container - YouTube

Print this item

  Accretion Disk
Posted by: OldMoses - Yesterday, 12:08 PM - Forum: Programs - Replies (10)

My goal with this one was to simulate a black hole's accretion disk using my standard vector library tools. It will eventually settle down to the entropic disk that I was after, but I wasn't expecting the initial splash patterns. I like how the particles will arrange themselves into discrete orbit shells. That said, this is NOT a gravity simulation, as attraction does not vary with distance.

Use the space bar to display a green "event horizon", which shows how close the orbits skim it.

It's an amusement to change some of the parameters and see how it affects the pattern and/or number of survivors.

The number in the upper right corner is the number of surviving particles, the rest are lost, forever lost! Wink

If you stare at it long enough the world will start spinning too. Maybe you might also begin to feel sleepy and have an irresistible urge to send me a sizeable check. Tongue

Code: (Select All)
OPTION _EXPLICIT

TYPE V2 '                                                       R2 vector format: used with R2_ subs & functions
    x AS SINGLE
    y AS SINGLE
END TYPE

TYPE particle
    ex AS INTEGER '                                             particle exists -1=yes 0=no
    p AS V2 '                                                   position
    d AS V2 '                                                   displacement aka movement
END TYPE

DIM c&, bs&, rs&
DIM vl%, vecs%, attrad%, wd%, ht%, eh%
DIM AS V2 tmp, op, cn
DIM speed!, attstr!

wd% = _DESKTOPWIDTH
ht% = _DESKTOPHEIGHT

SCREEN _NEWIMAGE(wd%, ht%, 32)
DO UNTIL _SCREENEXISTS: LOOP
_SCREENMOVE 0, 0
_TITLE "Accretion disk  {esc to quit}"

vecs% = 10000 '                                                 total starting particles
attrad% = 50 '                                                  radius of the attractor
attstr! = .1 '                                                  pull of the attractor
speed! = 1 '                                                    particle orbit velocity
bs& = &HFFAFAFFF '                                              particle color blue shift
rs& = &HFFFFAFAF '                                              particle color red shift

cn.x = _SHR(wd%, 1): cn.y = _SHR(ht%, 1) '                      screen center

'set initial particle data
RANDOMIZE TIMER
DIM SHARED V(vecs%) AS particle
FOR vl% = 0 TO UBOUND(V)
    V(vl%).ex = -1 '                                            particle exists at start
    DO
        V(vl%).p.x = INT(RND * wd%) '                           random position start
        V(vl%).p.y = INT(RND * wd%) - _SHR(wd% - ht%, 1)
        R2_P2P tmp, V(vl%).p, cn '                              get particle position vector <tmp> relative to screen center
    LOOP UNTIL R2_Mag(tmp) < _SHR(wd%, 1) '                     redo until particle within a circular area
    R2_Orth tmp, tmp, -1 '                                      convert position vector to an orthogonal rotation vector  - CCW  + CW
    R2_Norm V(vl%).d, tmp, speed! '                             set magnitude of rotation vector to speed! and assign it to particle displacement
NEXT vl%

'Main program loop
DO
    IF _KEYDOWN(32) THEN eh% = NOT eh%
    CLS
    _PRINTSTRING (0, 0), STR$(vecs%) '                          number of surviving particles
    IF eh% THEN CIRCLE (cn.x, cn.y), attrad%, &HFF00FF00 '      space bar to toggle the boundary of the attractor in green if desired
    FOR vl% = 0 TO UBOUND(V)
        IF NOT V(vl%).ex THEN _CONTINUE '                       skip particles that have been swallowed by attractor
        op = V(vl%).p
        R2_P2P tmp, op, cn '                                    obtain the inward pointing vector between particle and attractor
        c& = _IIF(R2_Dot(tmp, V(vl%).d) > 0, rs&, bs&) '        blue shift = approaching apogee, red shift = approaching perigee
        IF R2_Mag(tmp) < attrad% THEN '                         if particle is within "event horizon"
            V(vl%).ex = 0: vecs% = vecs% - 1 '                  particle has been absorbed by attractor
        ELSE
            R2_Norm tmp, tmp, attstr! '                         attractor force vector
            R2_Add V(vl%).d, tmp, 1 '                           add that to V(vl%).d
            R2_Add V(vl%).p, V(vl%).d, 1 '                      add displacement to position aka move it
            LINE (op.x, op.y)-(V(vl%).p.x, V(vl%).p.y), c& '    show the vector
        END IF
    NEXT vl%
    _LIMIT 30
    _DISPLAY
LOOP UNTIL _KEYDOWN(27)
END

'------------------------------------------------------------------------------
'SUBROUTINES
'------------------------------------------------------------------------------
SUB R2_P2P (re AS V2, st AS V2, nd AS V2)
    'ÞßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßÝ
    'Þ Return a 2D position vector <re> from point (st) to point (nd)          Ý
    'ÞÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝ
    re.x = nd.x - st.x
    re.y = nd.y - st.y
END SUB 'R2_P2P

'------------------------------------------------------------------------------
SUB R2_Add (re AS V2, se AS V2, m AS SINGLE)
    'ÞßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßÝ
    'Þ Adds (or subtracts) a scalar multiple (m) of vector <se> to vector <re> Ý
    'Þ if <re> is to be preserved in its prior state then a copy should be     Ý
    'Þ made for the subroutine call. Send a negative scalar (m) to subtract    Ý
    'Þ Returns: vector <re>                                                    Ý
    'ÞÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝ
    re.x = re.x + se.x * m
    re.y = re.y + se.y * m
END SUB 'R2_Add

'------------------------------------------------------------------------------
FUNCTION R2_Dot (v AS V2, v2 AS V2)
    'ÞßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßÝ
    'Þ Return scalar dot product between two V2 vectors <v> & <v2>            Ý
    'ÞÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝ
    R2_Dot = v.x * v2.x + v.y * v2.y
END FUNCTION 'R3_Dot

'------------------------------------------------------------------------------
FUNCTION R2_Mag! (v AS V2)
    'ÞßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßÝ
    'Þ Return the scalar magnitude of 2D vector (v)                           Ý
    'ÞÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝ
    R2_Mag! = _HYPOT(v.x, v.y)
END FUNCTION 'R2_Mag!

'------------------------------------------------------------------------------
SUB R2_Orth (re AS V2, v AS V2, d AS INTEGER)
    'ÞßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßÝ
    'Þ Returns vector <re> as a 90ø orthogonal of vector <v>. Sending a       Ý
    'Þ positive value for d rotates clockwise, while a negative value for     Ý
    'Þ d rotates counterclockwise. <re> & <v> can be the same.                Ý
    'ÞÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝ
    DIM x!, y!
    x! = v.x: y! = v.y
    re.x = y! * SGN(d)
    re.y = -x! * SGN(d)
END SUB 'R2_Orth

'------------------------------------------------------------------------------
SUB R2_Norm (re AS V2, v AS V2, scalar AS SINGLE)
    'ÞßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßßÝ
    'Þ Grow/Shrink V2 vector <v> to (scalar) length. <re> & <v> can be the    Ý
    'Þ same vectors, overwriting the original. Vector direction is preserved. Ý
    'Þ Returns: vector <re> as new length of <v>                              Ý
    'ÞÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝ
    DIM m!
    DIM t AS V2 '                                               temporary vector
    t = v '                                                     V vector to temporary container
    m! = R2_Mag!(t) '                                           magnitude of temporary
    IF m! = 0 THEN
        re.x = 0: re.y = 0 '                                    zero vector- no direction information available
    ELSE
        re.x = (t.x / m!) * scalar '                            reduce to unit and expand to scalar
        re.y = (t.y / m!) * scalar '                            and assign to return vector
    END IF
END SUB 'R2_Norm

Print this item

  Ready for Easter!
Posted by: bplus - 03-10-2026, 12:15 PM - Forum: Holiday Code - No Replies

I am.

   

   

I can make a million, all different Smile

Print this item

  Quick Sort for variable length strings
Posted by: bplus - 03-10-2026, 11:01 AM - Forum: Utilities - Replies (3)

Here is Quick Sort versus Comb Demo with 1000000 randomly chosen "words" <<< this means they aren't actually all words you can find in a dictionary, they are made randomly from this:
b$ = b$ + Mid$("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.?", (Rnd * 64) \ 1 + 1, 1))

Code: (Select All)
Option _Explicit
_Title "Comb Sort vrs Quick Sort" ' b+ 2023-05-30
Randomize Timer ' so we have a different array each time we compare

DefLng A-Z
Const nItems = 1000000
Dim sa$(1 To nItems) ' setup a string array sa$() to sort
Dim copy$(1 To nItems) ' make a copy of sa$() to compare another sort to
Dim As Long i, j ' indexes to array  for building and displaying the arrays
Dim As Long r '  a random posw integer = 2 to 6
Dim t##, qtime##, ctime##
Dim b$ ' building string
For i = 1 To nItems ' make a random list to sort
    b$ = ""
    r = (Rnd * 5) \ 1 + 2
    For j = 0 To r
        b$ = b$ + Mid$("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.?", (Rnd * 64) \ 1 + 1, 1)
    Next
    sa$(i) = b$
    copy$(i) = b$
    Print b$,
Next
Print
Print "Press any to Quick Sort"
Sleep
Cls
t## = Timer(.001)
QuickSort 1, nItems, sa$()
qtime## = Timer(.001) - t##
For i = 1 To 10
    Print sa$(i),
Next
Print: Print
For i = nItems - 9 To nItems
    Print sa$(i),
Next
Print: Print
Print "   Quick Sort time:"; qtime##
Print
Print "   Press any to Comb Sort with array copy, zzz..."
Print
Print
Sleep
t## = Timer(.001)
CombSort copy$()
ctime## = Timer(.001) - t##
For i = 1 To 10
    Print copy$(i),
Next
Print: Print
For i = nItems - 9 To nItems
    Print copy$(i),
Next
Print: Print
Print "   Comb Sort time:"; ctime##
Print
If ctime## < qtime## Then Print "   Comb winds!" Else Print "   QSort wins again!"


Sub QuickSort (start As Long, finish As Long, arr$())
    Dim Hi As Long, Lo As Long, Middle$
    Hi = finish: Lo = start
    Middle$ = arr$((Lo + Hi) / 2) 'find middle of arr$
    Do
        Do While arr$(Lo) < Middle$: Lo = Lo + 1: Loop
        Do While arr$(Hi) > Middle$: Hi = Hi - 1: Loop
        If Lo <= Hi Then
            Swap arr$(Lo), arr$(Hi)
            Lo = Lo + 1: Hi = Hi - 1
        End If
    Loop Until Lo > Hi
    If Hi > start Then Call QuickSort(start, Hi, arr$())
    If Lo < finish Then Call QuickSort(Lo, finish, arr$())
End Sub

' trans from johnno ref: https://rcbasic.freeforums.net/thread/779/sort-algorithms
Sub CombSort (arr$())
    Dim As Long itemCount, start, fini, swaps, gap, i
    start = LBound(arr$)
    itemCount = UBound(arr$) - start + 1
    fini = start + itemCount - 1
    gap = itemCount
    While gap > 1 Or swaps <> 0
        gap = Int(gap / 1.25)
        If gap < 1 Then gap = 1
        swaps = 0
        For i = start To itemCount - gap
            If arr$(i) > arr$(i + gap) Then
                Swap arr$(i), arr$(i + gap)
                swaps = 1
            End If
        Next
    Wend
End Sub

To use Quick Sort include the Sub in your code, of course, then call
QuickSort Lbound(array$), UBound(array$), array$()

PS you can sort parts of the array as well, just use lowest place, to highest place but who does that?

Simple as that! @PhilOfPerth

Print this item

  Sorting numbers - FiliSort
Posted by: 2112 - 03-08-2026, 04:34 PM - Forum: Utilities - Replies (11)

Code: (Select All)

' FiliSort v1.0
' This a very simple sorting method, a lot faster than Bubblesort
' For 50000 numbers in my pc, it takes 16.43 sec
' while the Bubble Sort takes 29.18 sec

Screen _NewImage(800, 700, 32)

Dim Shared tn As Long
tn = 20000 ' total numbers to sort
Dim Shared n(tn) As Single

Randomize Timer
For i = 1 To tn: n(i) = Rnd * 100 - 50: Next i

timer1! = Timer
Print "Sorting..."; tn; "numbers": Print "Timer starts:"; timer1!

FiliSort n()

timer2! = Timer
Print "Timer ends:"; timer2!; "    Seconds:"; (timer2! - timer1!) / 1.18

'For k = 1 To tn: Print n(k): Next k

End

Sub FiliSort (n())
    For k = 1 To tn / 2
        min = n(k): max = n(tn - k + 1)
        imin = 0: imax = 0
        For i = k To tn - k
            If min > n(i + 1) Then min = n(i + 1): imin = i + 1 'find the minimum of the numbers
            If max <= n(i) Then max = n(i): imax = i 'find the maximum of the numbers
        Next i
        If imin > 0 Then n(imin) = n(k): n(k) = min
        If imax = k Then imax = imin
        If imax > 0 Then n(imax) = n(tn - k + 1): n(tn - k + 1) = max
    Next k
End Sub

Print this item

  Arrays inside Types?
Posted by: Ikerkaz - 03-04-2026, 09:44 PM - Forum: General Discussion - Replies (47)

Hi to all.

In my early years as a Pascal programmer, I remember that I could do something like this:

Code: (Select All)
Type T_Thing
    Namez As String
    Index(4) as integer
End Type

And then use this:

Code: (Select All)
A.Index(1)=5

It will be very useful for me... is there any option in the actual QB64PE? Maybe in the future?

Thank you very much Smile

Print this item

  Ebook Organizer
Posted by: SMcNeill - 03-04-2026, 09:15 PM - Forum: SMcNeill - No Replies

An updated version of the ebook file/folder tool which I shared on here somewhere in the past:

Code: (Select All)
$Let RENAME = TRUE
$Let MOVEDIR = FALSE


Screen _NewImage(1280, 720, 32)

ReDim As String FileList(0), f '                be certain to reset your file listing to 0 files like this, before calling the recursive version
Dim i As Long

ChDir "Z:\Books Sorting\"
Disk.File.List "", "", 1, FileList() '      get a list of all files in the directory


$If RENAME Then
    'this will rename all the files from "LAST, FIRST -- BOOKTITLE" to "FIRST LAST -- BOOKTITLE"
    For i = 1 To UBound(FileList)
        b$ = FileList(i)
        comma = InStr(b$, ",")
        dash = InStr(b$, " - ")
        If comma <> 0 _AndAlso comma < dash Then
            n$ = Left$(b$, dash)
            last$ = _Trim$(Left$(n$, comma - 1))
            first$ = _Trim$(Mid$(n$, comma + 1))
            newname$ = first$ + " " + last$ + Mid$(FileList(i), InStr(FileList(i), " - "))
            Print FileList(i), newname$
            Name FileList(i) As newname$
        End If
    Next
$End If


$If MOVEDIR Then
    'this will then take the properly named books and put them in their own subfolders
    'so all books by Stephen King are now in a folder called Stephen King
    For i = 1 To UBound(FileList)
        filename$ = FileList(i)
        Print filename$; " =>";
        If _FileExists(filename$) Then
            period = _InStrRev(filename$, ".")
            If period Then
                file_no_ext$ = Left$(filename$, period - 1)
            Else
                file_no_ext$ = filename$
            End If
        Else
            Print "File not found.  Check paths for error."
            _Continue
        End If
        dash = InStr(file_no_ext$, " -")
        If dash Then file_no_ext$ = Left$(file_no_ext$, dash - 1)
        If _DirExists(file_no_ext$) = 0 Then MkDir file_no_ext$
        Name filename$ As file_no_ext$ + "\" + filename$
        Print file_no_ext$ + "\" + filename$

    Next
$End If






Sub Disk.File.List (SearchDir As String, Extension As String, Flag As Long, ReturnArray() As String)
    'flags are binary bits which represent the following
    'Note that a quick value of -1 will set all bits and return everything for us
    '1 -- file listing
    '2 -- directory listing
    '4 -- sorted (directory before file, like windows explorer does) -- implies 1 + 2 both are wanted.
    '8 -- return full path info
    Dim As Long FileCount, pass
    Dim As String Search, File, Slash
    ReDim ReturnArray(1000) As String
    If SearchDir = "" Then SearchDir = _CWD$: If Extension = "" Then Extension = "*"
    If InStr(_OS$, "WIN") Then Slash = "\" Else Slash = "/"
    If Right$(SearchDir, 1) <> "/" _AndAlso Right$(SearchDir, 1) <> "\" Then SearchDir = SearchDir + Slash
    Search = SearchDir + Extension
    If Flag And 4 Then 'sorted so we get directory listings then files
        For pass = 1 To 2 'two passes, first to get directory listings then files
            File = _Files$(Search)
            Do While Len(File)
                If File = ".\" _OrElse File = "..\" Then

                Else
                    If ((pass = 1) _AndAlso _DirExists(SearchDir + File)) _OrElse ((pass = 2) _AndAlso _FileExists(SearchDir + File)) Then
                        FileCount = FileCount + 1
                        If FileCount > UBound(ReturnArray) Then ReDim _Preserve ReturnArray(FileCount + 1000) As String
                        If Flag And 8 Then File = SearchDir + File 'we want the full path info
                        ReturnArray(FileCount) = File
                    End If
                End If
                File = _Files$
            Loop
        Next
    Else 'unsorted so files and directories are simply listed in alphabetical order
        File = _Files$(Search) 'one single pass where we just grab all the info at once
        Do While Len(File)
            If File = ".\" _OrElse File = "..\" Then

            Else
                If ((Flag And 1) _AndAlso _FileExists(SearchDir + File)) _OrElse ((Flag And 2) _AndAlso _DirExists(SearchDir + File)) Then
                    FileCount = FileCount + 1
                    If FileCount > UBound(ReturnArray) Then ReDim _Preserve ReturnArray(FileCount + 1000) As String
                    If Flag And 8 Then File = SearchDir + File 'we want the full path info
                    ReturnArray(FileCount) = File
                End If
            End If
            File = _Files$
        Loop
    End If
    ReDim _Preserve ReturnArray(FileCount) As String
End Sub


This simple little tool is the world's biggest timesaver for me.  It's not the type of thing that most folks get enthused about, but I'm in LOVE with it for the ease with which it helps to simplify my personal life.

I download books constantly from the interweb.  Project gutenberg and other such archive sites are absolutely astounding and utterly amazing to me, and it has been conjectured that I perhaps might also download books from torrents and other such things allegedly.

The problem with downloading books from different sources is they all have their own damn way of storing the info in the file names.  Ages and ages ago, I settled on a very simple format for my own archives and collection -- "Author First Name + Last Name + Book Series + Series Number + Book Title + Format"

Problem is, a lot of places like to put their crap in a different format than that.

So I wrote this little tool which simple takes "LAST", "FIRST" names and converts them into "FIRST" + " " + "LAST" names.  I just used this earlier and it renamed and corrected over 2000 book titles for me in the space of about 2 seconds.  It would've taken me DAYS to do each and every friggin one by hand manually!!

Then, to automate the process further, I run this a second time to automatically read those file names, use them to create the proper directory structure and then I move the books into those folders.

"King, Stephen -- Carrie.epub"  becomes "Stephen King\Stephen King -- Carrie.epub"

All in the space of about 5 seconds, I renamed 2000 books, put them in their own directories, and then once I gave them a good once over to make certain they looked like they should, I simply merged them into my existing library.

What would have taken me days to do manually, I did in moments with the help of QB64PE and this little automation tool.  Big Grin

I don't know if anyone else would ever find this useful for their own stuff, but I thought I'd share it anyway.  It's sure as hell useful for *ME*!!  Big Grin

Print this item

  4x4 Square Elimination Puzzle
Posted by: bplus - 03-04-2026, 07:31 PM - Forum: bplus - Replies (11)

Revisiting an old old game puzzle I made years ago! Then, I filled the board randomly with red and blue squares and had no idea if you could actually get down to only one red and one blue cell. So I revised the BoardSetup code to start with a red and blue cell at a given spot 1,1 and 2,1 for 4x4 and reversed engineered clicks to build a puzzle with an exact solution. Trouble WAS most puzzles built this way were trivial to solve ie a sea of red and a sea of blue with no islands of the opposite color to make the puzzle challenging. What I needed was an expansion function that could isolate a cell, one color surrounded by the opposite. Still it takes awhile to get a non trivial puzzle built but finally got it all worked out so you can use a little logic and solve each puzzle perfectly returning the Puzzle state back to the seed start state.

You have solved the Puzzle "good" when you can get it down to 2 cells:
   

You have solved the Puzzle "better" when you get 2 cells adjacent to each other:
   

You have Not solved the Puzzle when you see this:
   

And you have solved the Puzzle Perfectly with exact positioning of red and blue like this:
   

So here is the Puzzle code:

Code: (Select All)
_Title "4x4 Square Elimination Puzzle 2026-03-04" ' b+ port and severely updated mods from
'square elimination v4.bas SmallBASIC 0.12.7 [B+=MGA] 2016-10-03 one of the first games I ever made in BASIC

Randomize Timer
Screen _NewImage(400, 400, 32)
_Delay .25
_ScreenMove _Middle

Const BS = 360 'BS = Board Size in pixels notice 360 divisible 4, 9, 5
Dim Shared SPS, SPSm1, SS, State, LastX, LastY, Quit, WatchBoardSetup, MoreBlue, NRed, NBlue, SaveRX, SaveRY
' SPS = Squares Per Side this 4x4 Puzzle comes from a bigger version where you say how many SPS you want.
' SPSm1 = SPS minus 1
' SS = Square Side size in pixels
' State = tracks mouse clicks 0 no cliciks and 1 on 2nd click
' LastX, LastY = last mouse click
' Quit signals done with p[uzzle
' WatchBoardSetup for watching Puzzle builds to create non trivial puzzles
' MoreBlue used to balance number of red squares and blue
' NRed, NBlue track how many of each square used to rate solutions
' SaveRX, SaveRY used to track first red cell found, again for rating a puzzle solution.

ReDim Shared Board(1, 1) ' Board needed dynamic ReDim for various possible sizes

WatchBoardSetup = 0 ' watching board builds step by boring step, even watching just the expansions,
'                  it takes a lot of builds to get a board with an isolated cell = non trivial puzzle !!

Instructions ' only once for 4x4 puzzle
While _KeyDown(27) = 0
    'continue = Instructions 'gets the number of squares per side, SPS
    'If continue < 3 Or continue > 20 Then Stop Else SPS = continue
    SPS = 4
    SPSm1 = SPS - 1
    SS = BS / SPS

    State = 0
    Cls
    BoardSetup
    Update
    Quit = 0
    Do ' puzzle Loop
        While _MouseInput: Wend
        mx = _MouseX - 20: my = _MouseY: mb = _MouseButton(1)
        k$ = InKey$
        If k$ = "q" Or _KeyDown(27) Then Quit = 1
        If mb And mx < BS And my < BS Then
            _Delay .25
            col = Int(mx / SS)
            row = Int(my / SS)
            If State = 0 Then
                If (Board(col, row) = -1 Or Board(col, row) = 1) Then
                    LastX = col: LastY = row: State = 1
                End If
            Else
                moov = Moved(LastX, LastY, col, row)
                Select Case moov
                    Case 1 ' Moved up
                        For i = LastY To SPSm1
                            Board(LastX, i - 1) = Board(LastX, i)
                        Next
                        Board(LastX, SPSm1) = 0
                    Case 2 ' Moved right
                        For i = LastX To 0 Step -1
                            Board(i + 1, LastY) = Board(i, LastY)
                        Next
                        Board(0, LastY) = 0
                    Case 3 ' Moved down
                        For i = LastY To 0 Step -1
                            Board(LastX, i + 1) = Board(LastX, i)
                        Next
                        Board(LastX, 0) = 0
                    Case 4 ' Moved left
                        For i = LastX To SPSm1
                            Board(i - 1, LastY) = Board(i, LastY)
                        Next
                        Board(SPSm1, LastY) = 0
                End Select
                LastX = -10: LastY = -10: State = 0
            End If
            Update
            _Limit 30
        End If 'mouse on board
    Loop Until Quit
Wend
System

Sub BoardSetup
    'attempting to setup Board so that it is possible to eliminate squares down to 2 one of each color
    'and not be a trivial puzzle to solve ie 1 sea of red and one sea of blue, no islands of opposite color.
    ' ideally islands of blue in sea of red and vice versa BTW it doesn't work for 3x3.
    '
    ' Non trivial solutions have a least one isolated cell, that's what we will display for puzzle.

    ' If WatchBoardSetup Then Sleep code was for watching puzzle builds kinda fun except for stopping all

    ReDim Board(SPSm1, SPSm1) ' clears array
    ' seed
    Board(SPSm1 \ 2, SPSm1 \ 2) = -1 '    red cell at 1,1 for 4x4 board
    Board(SPSm1 \ 2 + 1, SPSm1 \ 2) = 1 ' blue cell at 2, 1 for 4x4 board
    DrawBoard
    Do While EmptySpace ' drawing board to show program is thinking up a non trivial puzzle, takes awhile
        try = 0
        While 1
            x = Int(Rnd * SPS): y = Int(Rnd * SPS)
            ok = 0

            If Board(x, y) = turn Then
                ok = Expand(x, y) ' <<< without one good one of these, the puzzle will be trivial!
                If ok Then
                    DrawBoard
                    If WatchBoardSetup Then Sleep
                End If

            ElseIf Board(x, y) = 0 Then ' this works but takes awhile to isolate
                'ElseIf Board(x, y) <> turn Then ' no this makes it harder to isolate

                '    ' Do I need ElseIf ? would I not get a harder board without it
                '    ' NO IT IS NEEDED! What if we try first? eh not terrible but...
                If LikeColorNextTo(turn, x, y) And Rnd < .1 Then ' decrease these  with rnd
                    Board(x, y) = turn: DrawBoard: ok = -1
                    ''dummy = Expand(x, y) ' test for more expansions, nope! ?
                End If

            End If
            If ok = 0 Then
                try = try + 1
                If try > SPSm1 ^ 3 Then
                    If turn = -1 Then turn = 1 Else turn = -1
                    try = 0
                End If
            Else
                Exit While
            End If
        Wend
        If MoreBlue Then turn = -1 Else turn = 1
    Loop
    ' will this work?  yes back this up 2026-03-02_11_11A

    ' now that board is set does it have an isolated cell? if not a 3x3 board
    If SPS <> 3 _AndAlso BoardHasIsolatedCell = 0 Then BoardSetup ' get us a non trivial board
    ' backup = SEG 2026-03-02_11_41A
End Sub

Function EmptySpace
    For y = 0 To SPSm1
        For x = 0 To SPSm1
            If Board(x, y) = 0 Then EmptySpace = -1: Exit Function
        Next
    Next
End Function

Function BoardHasIsolatedCell
    BoardHasIsolatedCell = 0 ' assume NOT and see if can find exception
    For y = 0 To SPSm1
        For x = 0 To SPSm1
            If LikeColorNextTo(Board(x, y), x, y) = 0 Then BoardHasIsolatedCell = -1: Exit Function
        Next
    Next
End Function

Function LikeColorNextTo (colr, x, y) ' for setup
    If x - 1 >= 0 Then
        If Board(x - 1, y) = colr Then LikeColorNextTo = -1: Exit Function
    End If
    If x + 1 <= SPSm1 Then
        If Board(x + 1, y) = colr Then LikeColorNextTo = -1: Exit Function
    End If
    If y - 1 >= 0 Then
        If Board(x, y - 1) = colr Then LikeColorNextTo = -1: Exit Function
    End If
    If y + 1 <= SPSm1 Then
        If Board(x, y + 1) = colr Then LikeColorNextTo = -1: Exit Function
    End If
    ' else LikeColorNextTo returns 0
End Function

Function Expand (x, y) ' this will leave x, y same color
    'and slide all cells in a direction that has an opening if there is one
    startDir = Int(Rnd * 4) + 1 ' pick random direction
    dir = startDir
    Do ' try each dir until we find an opening to expand
        Select Case dir
            Case 1 ' Move up
                If y <> 0 Then ' y is not at top so look for space
                    For yy = y - 1 To 0 Step -1
                        If Board(x, yy) = 0 Then 'yes a space, expand this dir
                            For yyy = yy To y - 1 ' reassign board cells but not x, y
                                Board(x, yyy) = Board(x, yyy + 1)
                            Next
                            Expand = -1: Exit Function
                        End If
                    Next yy
                End If
            Case 2 ' Move right
                If x <> SPSm1 Then ' not all the way right so look for space to right
                    For xx = x + 1 To SPSm1
                        If Board(xx, y) = 0 Then 'yes a space, expand this dir
                            For xxx = xx To x + 1 Step -1 'reassign from right to left
                                Board(xxx, y) = Board(xxx - 1, y) ' but leave x, y as is
                            Next
                            Expand = -1: Exit Function
                        End If
                    Next xx
                End If
            Case 3 ' Move down
                If y <> SPSm1 Then ' going down board, look for space to expand
                    For yy = y + 1 To SPSm1
                        If Board(x, yy) = 0 Then 'expand this dir
                            For yyy = yy To y + 1 Step -1 ' expand from bottom up
                                Board(x, yyy) = Board(x, yyy - 1) ' by reassign to above value
                            Next
                            Expand = -1: Exit Function
                        End If
                    Next yy
                End If
            Case 4 ' Move left
                If x <> 0 Then ' x is not at left of board, look for space
                    For xx = x - 1 To 0 Step -1
                        If Board(xx, y) = 0 Then 'yes a space! expand this dir
                            For xxx = xx To x - 1 ' reassign from left to right
                                Board(xxx, y) = Board(xxx + 1, y)
                            Next
                            Expand = -1: Exit Function
                        End If
                    Next xx
                End If
        End Select
        dir = dir + 1 ' still here? then dir failed so try next one unless we are back to startDir
        If dir = 5 Then dir = 1 ' dir 1 to 4 only
    Loop Until dir = startDir ' failed to expand at x, y  we are done here returning 0
End Function

Sub DrawBoard
    Line (0, 0)-(BS + 1, BS + 1), &HFF000000, BF
    NRed = 0: NBlue = 0
    For y = 0 To SPSm1
        For x = 0 To SPSm1
            If Board(x, y) = -1 Then
                c~& = &HFFFF0000: NRed = NRed + 1
                If NRed = 1 Then SaveRX = x: SaveRY = y ' <<< save these shared values for rating solution
            End If
            If Board(x, y) = 1 Then c~& = &HFF0000FF: NBlue = NBlue + 1
            If Board(x, y) = 0 Then c~& = &HFF000000
            Line (x * SS + 21, y * SS + 1)-Step(SS - 2, SS - 2), c~&, BF
        Next
    Next
    If NBlue > NRed Then MoreBlue = -1 Else MoreBlue = 0 ' flag for balancing red and blue
    _Display
End Sub

Sub Update ()
    Cls
    DrawBoard
    If PuzzleDone% Then ' messagebox that no moves are left and soluition is: good, better or perfect!
        rate = 1 ' = no more moves left
        ' has a perfect puzzle been played?
        ' 2 cells remain  = good
        If NBlue + NRed = 2 Then
            rate = 2
            ' the red is at 1,1 and the blue at 2,1 = Perfect!!!
            If Board(1, 1) = -1 _AndAlso Board(2, 1) = 1 Then
                rate = 3
            Else
                ' they are next to each other even better
                ' when I am drawing the board and counting cells I could save the location of
                ' the first red cell SaveRX, SaveRY
                If LikeColorNextTo(1, SaveRX, SaveRY) Then rate = 4
            End If

        End If ' 2 cells
        Select Case rate
            Case 1: _MessageBox "Puzzle Done", "No more moves are available.", "info"
            Case 2: _MessageBox "Puzzle Done", "Good job, only 2 Squares left!", "info"
            Case 3: _MessageBox "Puzzle Done", "A Perfect Puzzle solution!!!", "info"
            Case 4: _MessageBox "Puzzle Done", "Better than Good Solution, the 2 cells are adjacent.", "info"
        End Select
        Quit = 1
    Else
        _PrintString (46, 370), "esc or q to quit board and get new one."
        If State = 1 Then ' highlite  first cell clicked
            Line (LastX * SS + 21, LastY * SS)-Step(SS - 1, SS - 1), &HFFFFFFFF, B
        End If
        _Display
    End If
End Sub

Function Moved (x1, y1, x2, y2) ' moved returns the direction of move for sliding cells
    Moved = 0 ' not a legal move!
    If Board(x1, y1) = Board(x2, y2) Then 'same color and next to each other
        If x1 = x2 Then 'cols match
            If y1 - y2 = 1 Then Moved = 1 'up
            If y2 - y1 = 1 Then Moved = 3 'down
        Else
            If y1 = y2 Then 'rows match
                If x1 - x2 = 1 Then Moved = 4 'left
                If x2 - x1 = 1 Then Moved = 2 'right
            End If
        End If
    End If
End Function

Sub Instructions
    Cls
    Print '"12345678901234567890123456789012345678901234567890"
    Print "        The 4x4 Square Elimination Puzzle:"
    Print
    Print " The object is to eliminate as many squares as"
    Print " possible. A perfect puzzle leaves one red square"
    Print " and one blue square on the board next to each"
    Print " other: red on left at 1,1 blue on right 2,1."
    Print " that was how this Puzzle was seeded."
    Print
    Print " To eliminate a square, click the square to be"
    Print " removed. It will be highlighted, then click a"
    Print " like colored square directly above, below, left"
    Print " or right of the highlighted square. The square"
    Print " will be removed and all the squares behind it"
    Print " (if any) will be slid over one space."
    Print
    Print " If you change your mind after clicking a square,"
    Print " click that square again or a square of opposite"
    Print " color or a square of like color but not next to"
    Print " the square you clicked, to start again."
    Print
    Print
    Print "          Press any to continue... zzz"
    Sleep
End Sub

Function PuzzleDone%
    PuzzleDone% = -1 ' just find 2 like colored squares next to each other = there is a move left
    For y = 0 To SPSm1
        For x = 0 To SPSm1
            If Board(x, y) Then ' not a blank square
                If x + 1 <= SPSm1 Then ' horizontal move is left
                    If Board(x, y) = Board(x + 1, y) Then PuzzleDone% = 0: Exit Function
                End If
                If y + 1 <= SPSm1 Then ' vertical move is left
                    If Board(x, y) = Board(x, y + 1) Then PuzzleDone% = 0: Exit Function
                End If
            End If
        Next
    Next
End Function

I'd say good luck but like with Sudoku, it's all just Logic!

Print this item

  Neon Lights
Posted by: a740g - 03-04-2026, 11:06 AM - Forum: Programs - Replies (6)

I came across this neat little Haiku screensaver and couldn't resist porting it to QB64‑PE.

Enjoy!

[Image: Screenshot-2026-03-04-163128.png]

Code: (Select All)
' =========================================================================
' Neon Lights
' Ported from a Haiku C++ screensaver to QB64-PE by a740g
' Original Author: Adrien Destugues (MIT License)
' https://github.com/pulkomandy/neonlights
' =========================================================================

_DEFINE A-Z AS LONG
OPTION _EXPLICIT

$COLOR:32

CONST SCREEN_WIDTH = 1024
CONST SCREEN_HEIGHT = 768
CONST PARTICLES = 3000
CONST CITIES = 64
CONST SPOTS = 22 ' must be <= CITIES

TYPE City
    x AS SINGLE
    y AS SINGLE
    oldX AS SINGLE
    oldY AS SINGLE
    vx AS SINGLE
    vy AS SINGLE
    other AS LONG
    r AS LONG
    g AS LONG
    b AS LONG
END TYPE

DIM SHARED cities(0 TO CITIES - 1) AS City
DIM SHARED goodColor(0 TO 5) AS _UNSIGNED LONG
goodColor(0) = Blue
goodColor(1) = Cyan
goodColor(2) = Magenta
goodColor(3) = Green
goodColor(4) = Red
goodColor(5) = Yellow

SCREEN _NEWIMAGE(SCREEN_WIDTH, SCREEN_HEIGHT, 32)
_TITLE "Neon Lights"

RANDOMIZE TIMER

DIM frame AS LONG

DO
    IF (frame AND 1023) = 0 THEN
        Restart
    END IF

    DIM a AS LONG, b AS LONG, tr AS LONG
    DIM t AS SINGLE, dx AS SINGLE, dy AS SINGLE

    DIM n AS LONG
    WHILE n < PARTICLES
        a = INT(RND * SPOTS)
        tr = 0
        DO
            b = INT(RND * SPOTS)
            tr = tr + 1
        LOOP WHILE tr < 100 _ANDALSO CityDistance(a, b) < (SCREEN_WIDTH * SCREEN_HEIGHT) / (10 * (SCREEN_WIDTH + SCREEN_HEIGHT))

        IF tr < 100 THEN
            t = _PI(RND)

            dx = SIN(t) * (cities(b).x - cities(a).x) + cities(a).x
            dy = SIN(t) * (cities(b).y - cities(a).y) + cities(a).y

            dx = dx + (RND * 3!) - 1.5!
            dy = dy + (RND * 3!) - 1.5!

            DIM mixedR AS LONG: mixedR = (cities(a).r + cities(b).r) \ 2
            DIM mixedG AS LONG: mixedG = (cities(a).g + cities(b).g) \ 2
            DIM mixedB AS LONG: mixedB = (cities(a).b + cities(b).b) \ 2

            PSET (dx, dy), _RGB32(mixedR, mixedG, mixedB, 32)
        END IF

        n = n + 1
    WEND
    n = 0

    DIM c AS LONG
    WHILE c < SPOTS
        cities(c).vx = cities(c).vx + (cities(cities(c).other).x - cities(c).x) / SCREEN_WIDTH
        cities(c).vy = cities(c).vy + (cities(cities(c).other).y - cities(c).y) / SCREEN_HEIGHT

        cities(c).vx = cities(c).vx * 0.986!
        cities(c).vy = cities(c).vy * 0.979!

        IF RND < 0.01! THEN
            cities(c).other = INT(RND * SPOTS)
        END IF

        cities(c).x = cities(c).x + cities(c).vx
        cities(c).y = cities(c).y + cities(c).vy

        LINE (cities(c).oldX, cities(c).oldY)-(cities(c).x, cities(c).y), White

        cities(c).oldX = cities(c).x
        cities(c).oldY = cities(c).y

        c = c + 1
    WEND
    c = 0

    _DISPLAY
    _LIMIT 60

    frame = frame + 1
LOOP UNTIL _KEYHIT = _KEY_ESC

SYSTEM

SUB Restart
    CLS

    DIM tinc AS SINGLE: tinc = _PI(2! / SPOTS)

    DIM i AS LONG
    WHILE i < SPOTS
        cities(i).x = SCREEN_WIDTH \ 2
        cities(i).y = SCREEN_HEIGHT \ 2
        cities(i).oldX = cities(i).x
        cities(i).oldY = cities(i).y

        cities(i).vx = (1! + INT(RND * 11!)) * SIN(tinc * i)
        cities(i).vy = (1! + INT(RND * 11!)) * COS(tinc * i)

        DIM cIdx AS LONG: cIdx = INT(RND * 6!)
        cities(i).r = _RED32(goodColor(cIdx))
        cities(i).g = _GREEN32(goodColor(cIdx))
        cities(i).b = _BLUE32(goodColor(cIdx))

        DO
            cities(i).other = INT(RND * SPOTS)
        LOOP WHILE cities(i).other = i

        i = i + 1
    WEND
END SUB

FUNCTION CityDistance! (a AS LONG, b AS LONG)
    IF a <> b THEN
        DIM dx AS SINGLE: dx = cities(b).x - cities(a).x
        DIM dy AS SINGLE: dy = cities(b).y - cities(a).y
        CityDistance = SQR(dx * dx + dy * dy)
    END IF
END FUNCTION

Print this item

  '80s Song Randomizer
Posted by: Hiknefer - 03-03-2026, 07:31 AM - Forum: Utilities - Replies (3)

Greetings Folks,

This was one of the first programs I wrote when I discovered QB64PE, and it really shows. I thought I'd post it anyway in case anyone happens to find it useful. I collect '80s music and find it extremely helpful to be able to randomize all or some of the songs in my collection, either for listening or cleaning. Some of the file naming conventions I use are built into the program so that may or may not work for anyone else. It will randomize any group of songs of course, but it does have a number of '80s artist associations built in to better avoid two or more songs in a row by the same artist - hence the title.

Have a great day everyone!

Code: (Select All)
'$ExeIcon: '.\Icon.ico'

_Title "'80s Song Randomizer"

DefInt A - Z

Screen 12

'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

Const FALSE = 0
Const TRUE  = Not FALSE

Const NULL$        = ""
Const ESC$        = Chr$ (27)
Const CR$          = Chr$ (13)
Const BACKSPACE$  = Chr$ (8)

Const DOUBLEQUOTE$ = Chr$ (34)

'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

Const PROGRAMTITLE$ = "'80s Song Randomizer"
Const PROGRAMTITLE_R = 180
Const PROGRAMTITLE_G = 255
Const PROGRAMTITLE_B = 0

'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

Const INVALID          = 0
Const VALID            = 1
Const ILLEGALCHARACTER = 2

Const NOMATCH      = 0
Const POSSIBLEMATCH = 1
Const EXACTMATCH    = 2

Const RESULT_Y = 0
Const RESULT_N = 1
Const RESULT_R = 2

Const ASCENDING  = 0
Const DESCENDING = 1

'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

Const COLOR_PROMPT    = 13
Const COLOR_FILENAME  = 10
Const COLOR_FOLDERNAME = 11
Const COLOR_WARNING    = 14
Const COLOR_FAILURE    = 12

'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

Const MAXFILES = 20000

'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

Const VARIATION_FACTOR = .15 'The program will attempt to place songs by the same artist an equal distance apart, +/- (VARIATION_FACTOR * 100)%

'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

'Data for extended ASCII characters: Set these values to the correct values (before QB64 scrambles them).
'These values can be found in the "Extended ASCII Characters.pov" file.
'When adding a new value, also add a corresponding entry to the "FormatFileName_Screen$ ()" function.

Const BULLET$            = Chr$ (149)
Const CENT_SYMBOL$        = Chr$ (162)
Const DEGREE_SYMBOL$      = Chr$ (176)
Const SINGLE_QUOTE_LEFT$  = Chr$ (145)
Const SINGLE_QUOTE_RIGHT$ = Chr$ (146)
Const DOUBLE_QUOTE_LEFT$  = Chr$ (147)
Const DOUBLE_QUOTE_RIGHT$ = Chr$ (148)
Const ELIPSIS$            = Chr$ (133)
Const EXTENDED_DASH_0$    = Chr$ (150)
Const EXTENDED_DASH_1$    = Chr$ (151)
Const a_GRAVE$            = Chr$ (224)
Const a_ACUTE$            = Chr$ (225)
Const a_CIRCUMFLEX$      = Chr$ (226)
Const a_TILDE$            = Chr$ (227)
Const a_DIAERESIS$        = Chr$ (228)
Const a_RING$            = Chr$ (229)
Const e_GRAVE$            = Chr$ (232)
Const e_ACUTE$            = Chr$ (233)
Const e_CIRCUMFLEX$      = Chr$ (234)
Const e_DIAERESIS$        = Chr$ (235)
Const i_GRAVE$            = Chr$ (236)
Const i_ACUTE$            = Chr$ (237)
Const i_CIRCUMFLEX$      = Chr$ (238)
Const i_DIAERESIS$        = Chr$ (239)
Const n_TILDE$            = Chr$ (241)
Const o_GRAVE$            = Chr$ (242)
Const o_ACUTE$            = Chr$ (243)
Const o_CIRCUMFLEX$      = Chr$ (244)
Const o_TILDE$            = Chr$ (245)
Const o_DIAERESIS$        = Chr$ (246)
Const o_SLASH$            = Chr$ (248)
Const u_GRAVE$            = Chr$ (249)
Const u_ACUTE$            = Chr$ (250)
Const u_CIRCUMFLEX$      = Chr$ (251)
Const u_DIAERESIS$        = Chr$ (252)
Const y_ACUTE$            = Chr$ (253)
Const y_DIAERESIS$        = Chr$ (255)
Const A__GRAVE$          = Chr$ (192)
Const A__ACUTE$          = Chr$ (193)
Const A__CIRCUMFLEX$      = Chr$ (194)
Const A__TILDE$          = Chr$ (195)
Const A__DIAERESIS$      = Chr$ (196)
Const A__RING$            = Chr$ (197)
Const E__GRAVE$          = Chr$ (200)
Const E__ACUTE$          = Chr$ (201)
Const E__CIRCUMFLEX$      = Chr$ (202)
Const E__DIAERESIS$      = Chr$ (203)
Const I__GRAVE$          = Chr$ (204)
Const I__ACUTE$          = Chr$ (205)
Const I__CIRCUMFLEX$      = Chr$ (206)
Const I__DIAERESIS$      = Chr$ (207)
Const N__TILDE$          = Chr$ (209)
Const O__GRAVE$          = Chr$ (210)
Const O__ACUTE$          = Chr$ (211)
Const O__CIRCUMFLEX$      = Chr$ (212)
Const O__TILDE$          = Chr$ (213)
Const O__DIAERESIS$      = Chr$ (214)
Const O__SLASH$          = Chr$ (216)
Const U__GRAVE$          = Chr$ (217)
Const U__ACUTE$          = Chr$ (218)
Const U__CIRCUMFLEX$      = Chr$ (219)
Const U__DIAERESIS$      = Chr$ (220)
Const Y__ACUTE$          = Chr$ (221)
Const Y__DIAERESIS$      = Chr$ (159)

'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

Const SEPARATOR$ = " " + BULLET$ + " "

'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

Dim Shared ListOfSongs_Initialized$ (0 To MAXFILES)
Dim Shared ListOfSongs_Current$    (0 To MAXFILES)
Dim Shared ListOfSongs_New$        (0 To MAXFILES)
Dim Shared ListOfSongs_Best$        (0 To MAXFILES)

Dim Shared ListOfSongs_Artist$ (0 To 200)

Dim Shared CrossBandArtist$ (0 To 2000)

'PCG-32 variables
Dim Shared PCG_State~&& 'For PCG-32 Random Number Generator
Dim Shared PCG_Inc~&&  'For PCG-32 Random Number Generator

'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
'* *                                                                                                                * *
'* *                                                                                                                * *
'* * INTRODUCTION                                                                                                    * *
'* *                                                                                                                * *
'* *                                                                                                                * *
'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

Call DisplayProgramTitle (PROGRAMTITLE$, PROGRAMTITLE_R, PROGRAMTITLE_G, PROGRAMTITLE_B)

Color 7 : Print : Print "This program will rename all songs within a directory by giving them each a"
Print "prefix consisting of a unique random number. If the songs within the chosen"
Print "directory are already randomized, the old randomization data will be stripped"
Print "and new data will be created."

Print : Print "The program will identify cross-band artists (e.g." + DOUBLEQUOTE$ + "Blondie" + DOUBLEQUOTE$ + " / " + DOUBLEQUOTE$ +_
  "Deborah Harry" + DOUBLEQUOTE$ + ")."
Print "The data for these associations is contained within the program itself and may"
Print "need to be updated as new bands are added to the collection."

Color 15 : Print : Print "Note that song names containing a COLON, SLASH, or QUESTION MARK will be"
Print "skipped."

'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
'* *                                                                                                                * *
'* *                                                                                                                * *
'* * INITIALIZATION                                                                                                  * *
'* *                                                                                                                * *
'* *                                                                                                                * *
'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
'Read cross-band artist data

Restore CrossBandArtists

NCrossBandArtists = 0

Do
  Read CrossBandArtist$ (NCrossBandArtists), CrossBandArtist$ (NCrossBandArtists + 1)
  CrossBandArtist$ (NCrossBandArtists) = UCase$ (CrossBandArtist$ (NCrossBandArtists))
  CrossBandArtist$ (NCrossBandArtists + 1) = UCase$ (CrossBandArtist$ (NCrossBandArtists + 1))
  NCrossBandArtists = NCrossBandArtists + 2
Loop Until (CrossBandArtist$ (NCrossBandArtists - 2) = "*")

NCrossBandArtists = NCrossBandArtists - 2

'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
'Determine the last directory used

DirectoryFilename$ = "Directory.txt"

If _FileExists (DirectoryFilename$) Then
  DefaultDirectory$ = _ReadFile$ (DirectoryFilename$)
Else
  DefaultDirectory$ = NULL$
End If

'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
'Allow the user to select a current directory

Directory$ = _SelectFolderDialog$ ("Select the directory containing the files to rename:", DefaultDirectory$)

If (Directory$ = NULL$) Then
  System
End If

Directory$ = Directory$ + "\"

'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
'Save the current directory for the next use

_WriteFile DirectoryFilename$, Directory$

'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
'Read the filenames from the current directory

NSongs = 0
NSkippedSongs = 0

CurrentFileName$ = _Files$ (Directory$)

While Len (CurrentFileName$) > 0
  CurrentFileNameStatus = CheckValidFileName (CurrentFileName$)
  If (CurrentFileNameStatus = VALID) Then
      ListOfSongs_Initialized$ (NSongs) = CurrentFileName$
      NSongs = NSongs + 1
  End If
  If (CurrentFileNameStatus = ILLEGALCHARACTER) Then
      NSkippedSongs = NSkippedSongs + 1
  End If
  CurrentFileName$ = _Files$
Wend

If (NSongs < 2) Then
  Color COLOR_WARNING : Print : Print "There are too few songs in ";
  Color COLOR_FOLDERNAME : Print Directory$;
  Color COLOR_WARNING : Print "."
  Color 9
  End
End If

Call CheckForBadFileNames (NSongs, ListOfSongs_Initialized$ ())

'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
'Strip away old randomization data from filenames in memory and on the storage device

Confirmed = FALSE

For i = 0 To NSongs - 1
  EntryIsRandomized = FALSE
  If Left$ (ListOfSongs_Initialized$ (i), 1) = "[" Then
      L = InStr (ListOfSongs_Initialized$ (i), "] ")
      If (L > 0) Then
        EntryIsRandomized = TRUE
        For J = 2 To L - 1
            T$ = Mid$ (ListOfSongs_Initialized$ (i), J, 1)
            If (T$ < "0" Or T$ > "9") Then
              EntryIsRandomized = FALSE
              Exit For
            End If
        Next
        If (EntryIsRandomized And Not Confirmed) Then
            Color COLOR_WARNING : Print : Print "Warning: ";
            Color 7 : Print "Randomization data will be removed from the filenames in"
            Color COLOR_FOLDERNAME : Print Directory$;
            Color 7 : Print ". ";
            Color COLOR_PROMPT : Print "Continue? ";
            If (Not GetYN) Then
              Color 9
              End
            End If
            Confirmed = TRUE
        End If
        If (EntryIsRandomized) Then
            OldSongName$ = Directory$ + ListOfSongs_Initialized$ (i)
            ListOfSongs_Initialized$ (i) = Right$ (ListOfSongs_Initialized$ (i), Len (ListOfSongs_Initialized$ (i)) - L - 1)
            NewSongName$ = Directory$ + ListOfSongs_Initialized$ (i)
            Name OldSongName$ As NewSongName$
        End If
      End If
  End If
Next

'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
'Sort songs into descending order by number of songs per artist (5 step process)

'Step 1: Sort songs into ascending alphabetical order

If (NSongs > 1) Then
  Call QuickSort (0, NSongs - 1, ListOfSongs_Initialized$ (), ASCENDING)
End If

'Step 2: Determine how many digits are necessary

HighestCount = NSongs 'Don't subtract 1 in case all songs are by the same artist (songs will be numbered 1 .. N)

NDigits = 1

If (HighestCount >= 10) Then NDigits = 2
If (HighestCount >= 100) Then NDigits = 3
If (HighestCount >= 1000) Then NDigits = 4
If (HighestCount >= 10000) Then NDigits = 5

'Step 3: Add number of songs per artist to each song name

CurrentArtist$ = GetArtistName$ (ListOfSongs_Initialized$ (0))
NSongsByCurrentArtist = 1

For i = 1 To NSongs
  NextArtist$ = GetArtistName$ (ListOfSongs_Initialized$ (i))
  If (NextArtist$ <> CurrentArtist$) Then
      StartIndex = i - NSongsByCurrentArtist
      EndIndex = i - 1
      ArtistCount$ = GenerateDigit$ (NSongsByCurrentArtist, NDigits) + " "
      For J = StartIndex To EndIndex
        ListOfSongs_Initialized$ (J) = ArtistCount$ + ListOfSongs_Initialized$ (J)
      Next
      CurrentArtist$ = NextArtist$
      NSongsByCurrentArtist = 1
  Else
      NSongsByCurrentArtist = NSongsByCurrentArtist + 1
  End If
Next

'Step 4: Sort songs into descending order by number of songs per artist

If (NSongs > 1) Then
  Call QuickSort (0, NSongs - 1, ListOfSongs_Initialized$ (), DESCENDING)
End If

'Step 5: Remove number of songs per artist data from song names

For i = 0 To NSongs - 1
  ListOfSongs_Initialized$ (i) = Right$ (ListOfSongs_Initialized$ (i), Len (ListOfSongs_Initialized$ (i)) - NDigits - 1)
Next

'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
'Add dummy entry to the end of the song list to trigger the last group of songs

ListOfSongs_Initialized$ (NSongs) = "*" + SEPARATOR$ + "DUMMY ENTRY"

'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
'* *                                                                                                                * *
'* *                                                                                                                * *
'* * DISPLAY STATISTICS AND DETERMINE MODE OF OPERATION                                                              * *
'* *                                                                                                                * *
'* *                                                                                                                * *
'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

Color 7 : Print : Print "There are ";
Color 15 : Print LTrim$ (Str$ (NSongs));
Color 7 : Print " songs in ";
Color COLOR_FOLDERNAME : Print ShortenFolderName$ (Directory$);
Color 7 : Print "."

Print

If (NSkippedSongs > 0) Then
  Color COLOR_FAILURE : Print "DIRE WARNING: ";
End If

Color 15 : Print LTrim$ (Str$ (NSkippedSongs));
Color 7
If (NSkippedSongs = 1) Then
  Print " song was";
Else
  Print " songs were";
End If
Print " skipped due to a COLON, SLASH, or QUESTION MARK."

'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

Color COLOR_PROMPT
Print : Print "Attempt to randomize the song list continuously until no warnings occur? ";

If (GetYN) Then
  RunForever = TRUE
Else
  RunForever = FALSE
  _Delay .5
End If

'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
'* *                                                                                                                * *
'* *                                                                                                                * *
'* * START OF RANDOMIZATION ATTEMPTS LOOP                                                                            * *
'* *                                                                                                                * *
'* *                                                                                                                * *
'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

NAttempts_Collection~&& = 0

LeastNWeightedWarnings = 32760

Call PCG32_Seed ((MonthlyTimer~&))

Do

  If (Not RunForever) Then
      Call DisplayProgramTitle (PROGRAMTITLE$, PROGRAMTITLE_R, PROGRAMTITLE_G, PROGRAMTITLE_B)
  End If

  '* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  'Fill the song array with the initialized song list

  For i = 0 To NSongs
      ListOfSongs_Current$ (i) = ListOfSongs_Initialized$ (i)
      ListOfSongs_New$ (i) = NULL$
  Next

  '* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  'Randomize the song list

  CurrentArtist$ = GetArtistName$ (ListOfSongs_Current$ (0))
  NSongsByCurrentArtist = 1

  For i = 1 To NSongs

      NextArtist$ = GetArtistName$ (ListOfSongs_Current$ (i))

      If (NextArtist$ <> CurrentArtist$) Then

        TargetDistance = Int (NSongs / NSongsByCurrentArtist + .5)
        VFMultiplier = VARIATION_FACTOR * TargetDistance
        CurrentRandomLocation = FindRandomAvailable (NSongs)

        StartIndex = i - NSongsByCurrentArtist
        EndIndex = i - 1

        If (NSongsByCurrentArtist > 1) Then 'Randomize songs by the same artist

            For J = 0 To NSongsByCurrentArtist - 1 'Copy block of songs by the current artist into a working array
              ListOfSongs_Artist$ (J) = ListOfSongs_Current$ (StartIndex + J)
            Next

            MaxNVersionsOfOneSong = 1 'Determine the maximum number of versions of any one song by the current artist
            PreviousSongName$ = NULL$
            For J = 0 To NSongsByCurrentArtist - 1
              CurrentSongName$ = GetSongName$ (ListOfSongs_Artist$ (J))
              If (CurrentSongName$ <> PreviousSongName$) Then
                  NVersionsOfCurrentSong = 1
                  PreviousSongName$ = CurrentSongName$
              Else
                  NVersionsOfCurrentSong = NVersionsOfCurrentSong + 1
                  If (NVersionsOfCurrentSong > MaxNVersionsOfOneSong) Then
                    MaxNVersionsOfOneSong = NVersionsOfCurrentSong
                  End If
              End If
            Next

            Half = Int (NSongsByCurrentArtist / 2 + .1)

            If (MaxNVersionsOfOneSong <= Half) Then
              PerfectScore = NSongsByCurrentArtist
            Else
              PerfectScore = (NSongsByCurrentArtist - MaxNVersionsOfOneSong) * 2
            End If

            BestScoreSoFar = -1
            NAttempts_Artist = 0

            Do

              For J = 0 To NSongsByCurrentArtist - 1 'Scramble the working array
                  K = Int (PCG32_RandomFloat! * NSongsByCurrentArtist)
                  If (K <> J) Then Swap ListOfSongs_Artist$ (J), ListOfSongs_Artist$ (K)
              Next

              CurrentScore = 0 'Compute score for current combination of songs
              PreviousSongName$ = GetSongName$ (ListOfSongs_Artist$ (NSongsByCurrentArtist - 1))
              For J = 0 To NSongsByCurrentArtist - 1
                  CurrentSongName$ = GetSongName$ (ListOfSongs_Artist$ (J))
                  If (CurrentSongName$ <> PreviousSongName$) Then
                    CurrentScore = CurrentScore + 1
                    PreviousSongName$ = CurrentSongName$
                  End If
              Next

              If (CurrentScore > BestScoreSoFar) Then 'Update the master song list
                  For J = 0 To NSongsByCurrentArtist - 1
                    ListOfSongs_Current$ (StartIndex + J) = ListOfSongs_Artist$ (J)
                  Next
                  BestScoreSoFar = CurrentScore
              End If

              NAttempts_Artist = NAttempts_Artist + 1

            Loop Until (CurrentScore = PerfectScore Or NAttempts_Artist = 10000)

        End If

        For J = StartIndex To EndIndex
            ListOfSongs_New$ (CurrentRandomLocation) = ListOfSongs_Current$ (J)
            TargetDistanceOffset = Int (PCG32_RandomFloat! * VFMultiplier * 2 - VFMultiplier)
            CurrentRandomLocation = FindNextAvailable (CurrentRandomLocation + TargetDistance + TargetDistanceOffset, NSongs)
        Next

        CurrentArtist$ = NextArtist$
        NSongsByCurrentArtist = 1

      Else

        NSongsByCurrentArtist = NSongsByCurrentArtist + 1

      End If

  Next

  '* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  'Check for warnings

  NWarnings = 0
  NWeightedWarnings = 0

  Restart = FALSE

  CurrentArtist$ = GetArtistName$ (ListOfSongs_New$ (NSongs - 1))
  CurrentSongName$ = GetSongName$ (ListOfSongs_New$ (NSongs - 1))

  For i = 0 To NSongs - 1

      NextArtist$ = GetArtistName$ (ListOfSongs_New$ (i))
      NextSongName$ = GetSongName$ (ListOfSongs_New$ (i))

      L0 = Len (CurrentArtist$)
      L1 = Len (NextArtist$)

      TwoInARow = NOMATCH

      If (L0 = L1) Then

        If (NextArtist$ = CurrentArtist$) Then
            TwoInARow = EXACTMATCH
            NWeightedWarnings = NWeightedWarnings + 5 'Exact artist match counts heavily
            If (NextSongName$ = CurrentSongName$) Then
              NWeightedWarnings = NWeightedWarnings + 10 'Two versions of the same song by the same artist in a row
            End If
        End If

      Else

        Test0$ = " " + CurrentArtist$ + " "
        Test1$ = " " + NextArtist$ + " "

        If (L0 > L1) Then
            If (InStr (Test0$, Test1$) > 0) Then
              TwoInARow = POSSIBLEMATCH
              NWeightedWarnings = NWeightedWarnings + 4 'High probability of matching artists
            End If
        Else
            If (InStr (Test1$, Test0$) > 0) Then
              TwoInARow = POSSIBLEMATCH
              NWeightedWarnings = NWeightedWarnings + 4
            End If
        End If

      End If

      If (TwoInARow = NOMATCH) Then
        'Check for cross-band artists
        For J = 0 To NCrossBandArtists - 2 Step 2
            If (CurrentArtist$ = CrossBandArtist$ (J) Or NextArtist$ = CrossBandArtist$ (J)) Then
              If (CurrentArtist$ = CrossBandArtist$ (J + 1) Or NextArtist$ = CrossBandArtist$ (J + 1)) Then
                  TwoInARow = POSSIBLEMATCH
                  NWeightedWarnings = NWeightedWarnings + 3 'Matching artists, but possibly different vocalists
                  Exit For
              End If
            End If
        Next
      End If

      If (TwoInARow = NOMATCH) Then
        'Check for the same song by different artists
        If (NextSongName$ = CurrentSongName$) Then
            TwoInARow = POSSIBLEMATCH
            NWeightedWarnings = NWeightedWarnings + 1 'Lots of songs with the same name but totally different
        End If
      End If

      If (TwoInARow = EXACTMATCH Or TwoInARow = POSSIBLEMATCH) Then

        NWarnings = NWarnings + 1

        If (Not RunForever) Then

            PreviousIndex = i - 1
            If (PreviousIndex = -1) Then
              PreviousIndex = NSongs - 1
            End If

            PeriodLocation = _InStrRev (ListOfSongs_New$ (PreviousIndex), ".")
            Test0$ = Left$ (ListOfSongs_New$ (PreviousIndex), PeriodLocation - 1)

            PeriodLocation = _InStrRev (ListOfSongs_New$ (i), ".")
            Test1$ = Left$ (ListOfSongs_New$ (i), PeriodLocation - 1)

            Print

            If (TwoInARow = POSSIBLEMATCH) Then
              Color COLOR_WARNING : Print "WARNING: ";
              Color 7 : Print "Possibly two similar artists/bands or songs in a row:"
            Else
              Color COLOR_FAILURE : Print "DIRE WARNING: ";
              Color 7 : Print "Two songs by the same artist in a row:"
            End If

            Color COLOR_FILENAME
            Print FormatFileName_Screen$ (Test0$)
            Print FormatFileName_Screen$ (Test1$)

            If (NWarnings > 5) Then
              Color COLOR_PROMPT : Print : Print "Continue? ";
              Color 7 : Print "(Press" + DOUBLEQUOTE$ + "R" + DOUBLEQUOTE$ + " to restart the randomization process) ";
              Continue$ = Pick1Of3$ ("YES", "NO", "RESTART")
              If (Continue$ = "N") Then
                  Color 9
                  End
              End If
              If (Continue$ = "R") Then
                  Restart = TRUE
                  _Delay .5
                  Exit For
              End If
            End If

        End If

      End If

      CurrentArtist$ = NextArtist$
      CurrentSongName$ = NextSongName$

  Next

  If (Not RunForever And NWarnings > 0 And NWarnings <= 5) Then
      Color COLOR_PROMPT : Print : Print "Continue? ";
      Color 7 : Print "(Press" + DOUBLEQUOTE$ + "R" + DOUBLEQUOTE$ + " to restart the randomization process) ";
      Continue$ = Pick1Of3$ ("YES", "NO", "RESTART")
      If (Continue$ = "N") Then
        Color 9
        End
      End If
      If (Continue$ = "R") Then
        Restart = TRUE
        _Delay .5
      End If
  End If

  '* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

  If (RunForever) Then

      If (NWeightedWarnings < LeastNWeightedWarnings) Then
        For i = 0 To NSongs
            ListOfSongs_Best$ (i) = ListOfSongs_New$ (i)
        Next
        LeastNWeightedWarnings = NWeightedWarnings
      End If

      If (NWarnings > 0) Then
        NAttempts_Collection~&& = NAttempts_Collection~&& + 1
        If (NAttempts_Collection~&& = 100) Then
            Print
            CursorY = CsrLin
        End If

        If (NAttempts_Collection~&& Mod 100 = 0) Then
            Locate CursorY, 1
            Color 7 : Print "Attempts: ";
            Color 15 : Print Commatize$ (NAttempts_Collection~&&);
            Color 7 : Print "    Best score so far: ";
            Color 15 : Print LTrim$ (Str$ (LeastNWeightedWarnings)) + "    "
        End If

      End If

  End If

  Done = FALSE
  Aborted = FALSE

  If (InKey$ = ESC$) Then
      For i = 0 To NSongs
        ListOfSongs_New$ (i) = ListOfSongs_Best$ (i)
      Next
      Aborted = TRUE
      Done = TRUE
  End If

  If (NWarnings = 0) Then
      Done = TRUE
  End If

  If (Not RunForever And Not Restart) Then
      Done = TRUE
  End If

Loop Until (Done)

'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
'* *                                                                                                                * *
'* *                                                                                                                * *
'* * END OF LOOP                                                                                                    * *
'* *                                                                                                                * *
'* *                                                                                                                * *
'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

Print

If (Aborted) Then
  Color COLOR_WARNING : Print "Notice: ";
  Color 7 : Print "Attempts to randomize the song list until no warnings occur have been"
  Print "discontinued by the user. The best ordering found will be used."
End If

If (RunForever And Not Aborted) Then
  Color 15 : Print "Songs have been successfully randomized."
End If

If (Not RunForever) Then
  Color 15 : Print LTrim$ (Str$ (NWarnings));
  Color 7
  If (NWarnings = 1) Then
      Print " warning occurred."
  Else
      Print " warnings occurred."
  End If
End If

'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

Color COLOR_PROMPT : Print : Print "Would you like to update the filenames on the storage device? ";

If (Not GetYN) Then
  Color 9
  End
End If

'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

Print

StartNumber = InputNumber ("Start number: ", 1, 32000 - NSongs, COLOR_PROMPT)

If (StartNumber = -1) Then
  System
End If

'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

HighestCount = NSongs + StartNumber - 1

NDigits = 1

If (HighestCount >= 10) Then NDigits = 2
If (HighestCount >= 100) Then NDigits = 3
If (HighestCount >= 1000) Then NDigits = 4
If (HighestCount >= 10000) Then NDigits = 5

'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

For i = 0 To NSongs - 1
  OldSongName$ = Directory$ + ListOfSongs_New$ (i)
  NewSongName$ = Directory$ + "[" + GenerateDigit$ (i + StartNumber, NDigits) + "] " + ListOfSongs_New$ (i)
  Name OldSongName$ As NewSongName$
Next

'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

Color 15
Print : Print "Operation complete."

Color 9
End

'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
'* *                                                                                                                * *
'* *                                                                                                                * *
'* * CROSS-BAND ARTIST DATA                                                                                          * *
'* *                                                                                                                * *
'* *                                                                                                                * *
'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

CrossBandArtists:
'List artists/bands that should not be played consecutively. It is not necessary to list artist/band names wherein one
'artist/band name is entirely contained within the other, such as "Doro" and "Doro & Classic Night Orchestra."
Data "ABBA", "Agnetha Fältskog"
Data "Arabesque", "Enigma"
Data "Arabesque", "Sandra"
Data "B-52's", "Kate Pierson & Iggy Pop"
Data "Babies", "John Waite"
Data "Bad Company", "Firm"
Data "Bad English", "Babies"
Data "Bad English", "John Waite"
Data "Benjamin Orr", "Ric Ocasek"
Data "Black Sabbath", "Dio"
Data "Black Sabbath", "Rainbow"
Data "Blondie", "Deborah Harry"
Data "Boston", "Orion The Hunter"
Data "Cars", "Benjamin Orr"
Data "Cars", "Ric Ocasek"
Data "Chicago", "Peter Cetera"
Data "Chicago", "Peter Cetera & Amy Grant"
Data "Damn Yankees", "Ted Nugent"
Data "Damn Yankees", "Tommy Shaw"
Data "Duran Duran", "Arcadia"
Data "English Beat", "General Public"
Data "Enigma", "Sandra"
Data "Eurythmics", "Annie Lennox"
Data "Fleetwood Mac", "Stevie Nicks"
Data "Fleetwood Mac", "Stevie Nicks & Tom Petty And The Heartbreakers"
Data "FM Attack & Kristine", "Shadoworks & Kristine"
Data "Foreigner", "Lou Gramm"
Data "Genesis", "Peter Gabriel"
Data "Genesis", "Phil Collins"
Data "Genesis", "Phil Collins & Philip Bailey"
Data "Glass Tiger", "Bryan Adams"
Data "Go-Go's", "Belinda Carlisle"
Data "Heart", "Mike Reno & Ann Wilson"
Data "Heart", "Nancy Wilson"
Data "Honeydrippers", "Robert Plant"
Data "Jan Hammer", "London Starlight Orchestra"
Data "Journey", "Steve Perry"
Data "Katz", "Nilla Backman"
Data "Keel", "Badlands House Band"
Data "Kingdom Come", "Stone Fury"
Data "Loverboy", "Mike Reno & Ann Wilson"
Data "Miami Sound Machine", "Gloria Estefan"
Data "Mike + The Mechanics", "Paul Carrack"
Data "Mike + The Mechanics", "Paul Young"
Data "New Order", "Electronic"
Data "Night Ranger", "Damn Yankees"
Data "Rainbow", "Dio"
Data "Roxy Music", "Bryan Ferry"
Data "Smiths", "Morrissey"
Data "Soft Cell", "Marc Almond"
Data "Styx", "Damn Yankees"
Data "Styx", "Tommy Shaw"
Data "Van Halen", "David Lee Roth"
Data "Van Halen", "Sammy Hagar"
Data "Wall of Voodoo", "Stan Ridgway"
Data "Warlock", "Doro"
Data "Warlock", "Doro & Classic Night Orchestra"

'Not currently in use:
'Data "ABBA", "Frida"
'Data "Black Sabbath", "Ozzy Osbourne"
'Data "Black Sabbath", "Ozzy Osbourne & Lita Ford"
'Data "Culture Club", "Boy George"
'Data "Eagles", "Don Henley"
'Data "Eagles", "Glenn Frey"
'Data "Police", "Sting"
'Data "Propaganda", "Claudia Brücken"
'Data "Scandal", "Patty Smyth"
'Data "Siouxsie And The Banshees", "Creatures"

Data "*", "*"

'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
'* *                                                                                                                * *
'* *                                                                                                                * *
'* * MACROS                                                                                                          * *
'* *                                                                                                                * *
'* *                                                                                                                * *
'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

Sub DisplayProgramTitle (X_Title$, X_R, X_G, X_B)

  Title$ = X_Title$
  R = X_R
  G = X_G
  B = X_B

  Cls

  _PaletteColor 1, _RGB32 (R * .5, G * .5, B * .5)
  _PaletteColor 2, _RGB32 (R * .87, G * .87, B * .87)
  _PaletteColor 3, _RGB32 (R, G, B)

  Print : Print Space$ (40 - (Len (Title$) + 12) / 2);

  Color 1 : Print " ";
  Color 2 : Print " ";
  Color 3 : Print " ";

  Color 15 : Print Title$;
  Color 3 : Print " ";
  Color 2 : Print " ";
  Color 1 : Print " "

End Sub 'DisplayProgramTitle



Function ReportBadFileName (X_Message0Color, X_Message0$, X_Message1$, X_FileName$, X_PauseForEachBadFileName)

  Message0Color          = X_Message0Color
  Message0$              = X_Message0$
  Message1$              = X_Message1$
  FileName$              = X_FileName$
  PauseForEachBadFileName = X_PauseForEachBadFileName

  Continue$ = "S"

  Color Message0Color : Print : Print Message0$ + " ";
  Color 7 : Print Message1$

  Color COLOR_FILENAME : Print FormatFileName_Screen$ (FileName$)

  If (PauseForEachBadFileName) Then
      Color COLOR_PROMPT : Print "Continue? ";
      Color 7 : Print "(Press " + DOUBLEQUOTE$ + "S" + DOUBLEQUOTE$ + " to skip confirmations) ";
      Continue$ = Pick1Of3$ ("YES", "NO", "SKIP")
      If (Continue$ = "N") Then
        Color 9
        End
      End If
  End If

  If (Continue$ = "S") Then
      ReportBadFileName = FALSE
  Else
      ReportBadFileName = TRUE
  End If

End Function 'ReportBadFileName



Sub CheckForBadFileNames (X_NFileNames, FileName$ ())

  NFileNames = X_NFileNames

  SFN$ = "Suspicious file name:"
  BFN$ = "Bad file name:"

  ConfirmEachBadFileName = TRUE

  For I = 0 To NFileNames - 1

      CurrentFileName$ = FileName$ (I)

      NBrackets = 0
      NParenthesis = 0
      PreviousChar$ = " "
      BadLowerCaseChar = FALSE

      L = Len (CurrentFileName$)

      For J = 1 To L
        CurrentChar$ = Mid$ (CurrentFileName$, J, 1)
        If (CurrentChar$ = "[") Then NBrackets = NBrackets + 1
        If (CurrentChar$ = "]") Then NBrackets = NBrackets - 1
        If (CurrentChar$ = "(") Then NParenthesis = NParenthesis + 1
        If (CurrentChar$ = ")") Then NParenthesis = NParenthesis - 1
        If (NBrackets = 0 And CurrentChar$ >= "a" And CurrentChar$ <= "z" And PreviousChar$ = " ") Then
            BadLowerCaseChar = TRUE
        End If
        PreviousChar$ = CurrentChar$
      Next

      If (BadLowerCaseChar) Then
        ConfirmEachBadFileName = ReportBadFileName (COLOR_WARNING, SFN$, "(lower case character)", CurrentFileName$, ConfirmEachBadFileName)
      End If

      If (NBrackets <> 0) Then
        ConfirmEachBadFileName = ReportBadFileName (COLOR_FAILURE, BFN$, "(mis-matched brackets)", CurrentFileName$, ConfirmEachBadFileName)
      End If

      If (NParenthesis <> 0) Then
        ConfirmEachBadFileName = ReportBadFileName (COLOR_FAILURE, BFN$, "(mis-matched parenthesis)", CurrentFileName$, ConfirmEachBadFileName)
      End If

      If (InStr (CurrentFileName$, "][") <> 0) Then
        ConfirmEachBadFileName = ReportBadFileName (COLOR_FAILURE, BFN$, "(missing space between brackets)", CurrentFileName$, ConfirmEachBadFileName)
      End If

      If (InStr (CurrentFileName$, "  ") <> 0) Then
        ConfirmEachBadFileName = ReportBadFileName (COLOR_FAILURE, BFN$, "(double spaces)", CurrentFileName$, ConfirmEachBadFileName)
      End If

      If (InStr (CurrentFileName$, " .") <> 0) Then
        ConfirmEachBadFileName = ReportBadFileName (COLOR_FAILURE, BFN$, "(extra space)", CurrentFileName$, ConfirmEachBadFileName)
      End If

      If (InStr (CurrentFileName$, "...") <> 0) Then
        ConfirmEachBadFileName = ReportBadFileName (COLOR_FAILURE, BFN$, "(periods instead of ellipsis)", CurrentFileName$, ConfirmEachBadFileName)
      End If

      If (InStr (CurrentFileName$, SINGLE_QUOTE_LEFT$) <> 0) Then
        ConfirmEachBadFileName = ReportBadFileName (COLOR_FAILURE, BFN$, "(wrong single quote mark)", CurrentFileName$, ConfirmEachBadFileName)
      End If

      If (InStr (CurrentFileName$, SINGLE_QUOTE_RIGHT$) <> 0) Then
        ConfirmEachBadFileName = ReportBadFileName (COLOR_FAILURE, BFN$, "(wrong single quote mark)", CurrentFileName$, ConfirmEachBadFileName)
      End If

      If (InStr (CurrentFileName$, DOUBLE_QUOTE_LEFT$) <> 0) Then
        ConfirmEachBadFileName = ReportBadFileName (COLOR_FAILURE, BFN$, "(wrong double quote mark - use ASCII symbol #148)", CurrentFileName$, ConfirmEachBadFileName)
      End If

      If (InStr (CurrentFileName$, EXTENDED_DASH_0$) <> 0) Then
        ConfirmEachBadFileName = ReportBadFileName (COLOR_FAILURE, BFN$, "(wrong dash mark)", CurrentFileName$, ConfirmEachBadFileName)
      End If

      If (InStr (CurrentFileName$, EXTENDED_DASH_1$) <> 0) Then
        ConfirmEachBadFileName = ReportBadFileName (COLOR_FAILURE, BFN$, "(wrong dash mark)", CurrentFileName$, ConfirmEachBadFileName)
      End If

      If (InStr (CurrentFileName$, SEPARATOR$) = 0) Then
        ConfirmEachBadFileName = ReportBadFileName (COLOR_FAILURE, BFN$, "(missing artist/song name separator)", CurrentFileName$, ConfirmEachBadFileName)
      End If

      TempFileName$ = UCase$ (CurrentFileName$)

      PeriodLocation = _InStrRev (TempFileName$, ".")

      TempFileName$ = Left$ (TempFileName$, PeriodLocation - 1)

      PeriodLocation = _InStrRev (TempFileName$, ".")

      If (PeriodLocation <> 0) Then
        Extension$ = Right$ (TempFileName$, Len (TempFileName$) - PeriodLocation)
        If (                      _
            Extension$ = "FLAC" Or _
            Extension$ = "MP1"  Or _
            Extension$ = "MP2"  Or _
            Extension$ = "MP3"  Or _
            Extension$ = "M4A"  Or _
            Extension$ = "OGG"  Or _
            Extension$ = "WAV"    _
        ) Then
            ConfirmEachBadFileName = ReportBadFileName (COLOR_FAILURE, BFN$, "(extra extension)", CurrentFileName$, ConfirmEachBadFileName)
        End If
      End If

      'If (InStr (CurrentFileName$, BULLET$) <> _InStrRev (CurrentFileName$, BULLET$)) Then
      '  ConfirmEachBadFileName = ReportBadFileName (COLOR_FAILURE, BFN$, "(conflicting song name separators) ", CurrentFileName$, ConfirmEachBadFileName)
      'End If

  Next

End Sub 'CheckForBadFileNames



Function FindRandomAvailable (X_NSongs)

  NSongs = X_NSongs

  Dim AvailableSpot (0 To NSongs - 1)

  NAvailableSpots = 0

  For I = 0 To NSongs - 1
      If (ListOfSongs_New$ (I) = NULL$) Then
        AvailableSpot (NAvailableSpots) = I
        NAvailableSpots = NAvailableSpots + 1
      End If
  Next

  FindRandomAvailable = AvailableSpot (Int (PCG32_RandomFloat! * NAvailableSpots))

End Function 'FindRandomAvailable



Function FindNextAvailable (X_StartLocation, X_NSongs)

  StartLocation = X_StartLocation
  NSongs        = X_NSongs

  For I = 0 To NSongs - 1
      AdjustedIndex0 = (StartLocation + I) Mod NSongs
      AdjustedIndex1 = (StartLocation - I) Mod NSongs
      If (AdjustedIndex1 < 0) Then AdjustedIndex1 = NSongs + AdjustedIndex1
      If (ListOfSongs_New$ (AdjustedIndex0) = NULL$) Then AdjustedIndex = AdjustedIndex0 : Exit For
      If (ListOfSongs_New$ (AdjustedIndex1) = NULL$) Then AdjustedIndex = AdjustedIndex1 : Exit For
  Next

  If (AdjustedIndex0 <> AdjustedIndex1 And ListOfSongs_New$ (AdjustedIndex0) = NULL$ And ListOfSongs_New$ (AdjustedIndex1) = NULL$) Then
      Which = Int (PCG32_RandomFloat! * 2)
      If (Which = 0) Then
        AdjustedIndex = AdjustedIndex0
      Else
        AdjustedIndex = AdjustedIndex1
      End If
  End If

  FindNextAvailable = AdjustedIndex

End Function 'FindNextAvailable



Function GetYN ()

  Color 15 : Print "_";

  Locate CsrLin, Pos (0) - 1

  Do
      _Limit 20
      KeyPress$ = UCase$ (InKey$)
  Loop Until (KeyPress$ = "Y" Or KeyPress$ = "N" Or KeyPress$ = ESC$)

  If (KeyPress$ = ESC$) Then
      System
  End If

  If (KeyPress$ = "Y") Then
      Answer = TRUE
      Print "YES"
  Else
      Answer = FALSE
      Print "NO"
  End If

  GetYN = Answer

End Function 'GetYN



Function Pick1Of3$ (X_Option0$, X_Option1$, X_Option2$)

  Option0$ = X_Option0$
  Option1$ = X_Option1$
  Option2$ = X_Option2$

  K0$ = UCase$ (Left$ (Option0$, 1))
  K1$ = UCase$ (Left$ (Option1$, 1))
  K2$ = UCase$ (Left$ (Option2$, 1))

  Color 15 : Print "_";

  Locate CsrLin, Pos (0) - 1

  Do
      _Limit 20
      KeyPress$ = UCase$ (InKey$)
  Loop Until (KeyPress$ = K0$ Or KeyPress$ = K1$ Or KeyPress$ = K2$ Or KeyPress$ = ESC$)

  If (KeyPress$ = ESC$) Then
      System
  End If

  Select Case KeyPress$
      Case K0$:
        Print Option0$
      Case K1$:
        Print Option1$
      Case K2$:
        Print Option2$
  End Select

  Pick1Of3$ = KeyPress$

End Function 'Pick1Of3$



Function GetArtistName$ (X_FileName$)

  ArtistName$ = X_FileName$

  ArtistName$ = UCase$ (ArtistName$)

  I = InStr (ArtistName$, SEPARATOR$)

  ArtistName$ = RTrim$ (Left$ (ArtistName$, I - 1)) 'RTrim$ in case there's an extra space

  GetArtistName$ = ArtistName$

End Function 'GetArtistName$



Function GetSongName$ (X_FileName$)

  SongName$ = X_FileName$

  SongName$ = UCase$ (SongName$)

  PeriodLocation = _InStrRev (SongName$, ".")
  SongName$ = Left$ (SongName$, PeriodLocation - 1)

  I = InStr (SongName$, SEPARATOR$)

  SongName$ = Right$ (SongName$, Len (SongName$) - (I + 2))

  L = Len (SongName$)
  NBrackets = 0
  TempSongName$ = NULL$

  For I = 1 To L
      CurrentChar$ = Mid$ (SongName$, I, 1)
      If (CurrentChar$ = "[") Then NBrackets = NBrackets + 1
      If (NBrackets = 0) Then
        TempSongName$ = TempSongName$ + CurrentChar$
      End If
      If (CurrentChar$ = "]") Then NBrackets = NBrackets - 1
  Next

  SongName$ = RTrim$ (TempSongName$)

  GetSongName$ = SongName$

End Function 'GetSongName$



Function CheckValidFileName (X_FileName$)

  FileName$ = X_FileName$

  FileName$ = UCase$ (FileName$)

  FileNameStatus = VALID

  PeriodLocation = _InStrRev (FileName$, ".")

  If PeriodLocation = 0 Then
      FileNameStatus = INVALID
  Else
      Extension$ = Right$ (FileName$, Len (FileName$) - PeriodLocation)
      If not (                  _
        Extension$ = "FLAC" Or _
        Extension$ = "MP1"  Or _
        Extension$ = "MP2"  Or _
        Extension$ = "MP3"  Or _
        Extension$ = "M4A"  Or _
        Extension$ = "OGG"  Or _
        Extension$ = "WAV"    _
      ) Then
        FileNameStatus = INVALID
      End If
  End If

  If (InStr (FileName$, SEPARATOR$) = 0) Then
      FileNameStatus = INVALID
  End If

  If (InStr (FileName$, ":") <> 0 Or InStr (FileName$, "/") <> 0 Or InStr (FileName$, "?") <> 0) Then
      FileNameStatus = ILLEGALCHARACTER
  End If

  CheckValidFileName = FileNameStatus

End Function 'CheckValidFileName



Sub QuickSort (ListStart, ListEnd, ListElement$ (), SortOrder)

  High = ListEnd
  Low = ListStart
  Middle$ = UCase$ (ListElement$ ((Low + High) / 2))

  Do
      Select Case SortOrder
        Case ASCENDING:
            Do While (UCase$ (ListElement$ (Low)) < Middle$) : Low = Low + 1 : Loop
            Do While (UCase$ (ListElement$ (High)) > Middle$) : High = High - 1 : Loop
        Case DESCENDING:
            Do While (UCase$ (ListElement$ (Low)) > Middle$) : Low = Low + 1 : Loop
            Do While (UCase$ (ListElement$ (High)) < Middle$) : High = High - 1 : Loop
      End Select
      If (Low <= High) Then
        Swap ListElement$ (Low), ListElement$ (High)
        Low = Low + 1
        High = High - 1
      End If
  Loop Until (Low > High)

  If (High > ListStart) Then Call QuickSort (ListStart, High, ListElement$ (), SortOrder)
  If (Low < ListEnd) Then Call QuickSort (Low, ListEnd, ListElement$ (), SortOrder)

End Sub 'QuickSort



Function GenerateDigit$ (X_Value, X_NDigits)

  Value  = X_Value
  NDigits = X_NDigits

  Digits$ = LTrim$ (Str$ (Value))

  While (Len (Digits$) < NDigits)
      Digits$ = "0" + Digits$
  Wend

  GenerateDigit$ = Digits$

End Function 'GenerateDigit$



Function FormatFileName_Screen$ (X_FileName$)

  'These characters can be copy-and-pasted from the "Extended ASCII Characters.bas" file.

  FileName$ = X_FileName$

  NewFileName$ = NULL$

  For I = 1 To Len (FileName$)
      CurrentChar$ = Mid$ (FileName$, I, 1)
      Select Case CurrentChar$
        Case Chr$ (32) To Chr$ (127): NewFileName$ = NewFileName$ + CurrentChar$
        Case BULLET$:                NewFileName$ = NewFileName$ + "ù"
        Case CENT_SYMBOL$:            NewFileName$ = NewFileName$ + "›"
        Case DEGREE_SYMBOL$:          NewFileName$ = NewFileName$ + "§"
        Case SINGLE_QUOTE_LEFT$:      NewFileName$ = NewFileName$ + "'"
        Case SINGLE_QUOTE_RIGHT$:    NewFileName$ = NewFileName$ + "'"
        Case DOUBLE_QUOTE_LEFT$:      NewFileName$ = NewFileName$ + DOUBLEQUOTE$
        Case DOUBLE_QUOTE_RIGHT$:    NewFileName$ = NewFileName$ + DOUBLEQUOTE$
        Case ELIPSIS$:                NewFileName$ = NewFileName$ + "..." 'QB64 doesn't include the correct character
        Case EXTENDED_DASH_0$:        NewFileName$ = NewFileName$ + "-"
        Case EXTENDED_DASH_1$:        NewFileName$ = NewFileName$ + "-"
        Case a_GRAVE$:                NewFileName$ = NewFileName$ + "…"
        Case a_ACUTE$:                NewFileName$ = NewFileName$ + "a" 'QB64 doesn't include the correct character
        Case a_CIRCUMFLEX$:          NewFileName$ = NewFileName$ + "ƒ"
        Case a_TILDE$:                NewFileName$ = NewFileName$ + "a" 'QB64 doesn't include the correct character
        Case a_DIAERESIS$:            NewFileName$ = NewFileName$ + "„"
        Case a_RING$:                NewFileName$ = NewFileName$ + "†"
        Case e_GRAVE$:                NewFileName$ = NewFileName$ + "Š"
        Case e_ACUTE$:                NewFileName$ = NewFileName$ + "‚"
        Case e_CIRCUMFLEX$:          NewFileName$ = NewFileName$ + "ˆ"
        Case e_DIAERESIS$:            NewFileName$ = NewFileName$ + "‰"
        Case i_GRAVE$:                NewFileName$ = NewFileName$ + "i" 'QB64 doesn't include the correct character
        Case i_ACUTE$:                NewFileName$ = NewFileName$ + "¡"
        Case i_CIRCUMFLEX$:          NewFileName$ = NewFileName$ + "Œ"
        Case i_DIAERESIS$:            NewFileName$ = NewFileName$ + "‹"
        Case n_TILDE$:                NewFileName$ = NewFileName$ + "¤"
        Case o_GRAVE$:                NewFileName$ = NewFileName$ + "•"
        Case o_ACUTE$:                NewFileName$ = NewFileName$ + "¢"
        Case o_CIRCUMFLEX$:          NewFileName$ = NewFileName$ + "“"
        Case o_TILDE$:                NewFileName$ = NewFileName$ + "o" 'QB64 doesn't include the correct character
        Case o_DIAERESIS$:            NewFileName$ = NewFileName$ + "”"
        Case o_SLASH$:                NewFileName$ = NewFileName$ + "o" 'QB64 doesn't include the correct character
        Case u_GRAVE$:                NewFileName$ = NewFileName$ + "—"
        Case u_ACUTE$:                NewFileName$ = NewFileName$ + "£"
        Case u_CIRCUMFLEX$:          NewFileName$ = NewFileName$ + "–"
        Case u_DIAERESIS$:            NewFileName$ = NewFileName$ + "u" 'QB64 doesn't include the correct character
        Case y_ACUTE$:                NewFileName$ = NewFileName$ + "y" 'QB64 doesn't include the correct character
        Case y_DIAERESIS$:            NewFileName$ = NewFileName$ + "˜"
        Case A__GRAVE$:              NewFileName$ = NewFileName$ + "A" 'QB64 doesn't include the correct character
        Case A__ACUTE$:              NewFileName$ = NewFileName$ + "A" 'QB64 doesn't include the correct character
        Case A__CIRCUMFLEX$:          NewFileName$ = NewFileName$ + "A" 'QB64 doesn't include the correct character
        Case A__TILDE$:              NewFileName$ = NewFileName$ + "A" 'QB64 doesn't include the correct character
        Case A__DIAERESIS$:          NewFileName$ = NewFileName$ + "Ž"
        Case A__RING$:                NewFileName$ = NewFileName$ + "A" 'QB64 doesn't include the correct character
        Case E__GRAVE$:              NewFileName$ = NewFileName$ + "E" 'QB64 doesn't include the correct character
        Case E__ACUTE$:              NewFileName$ = NewFileName$ + "E" 'QB64 doesn't include the correct character
        Case E__CIRCUMFLEX$:          NewFileName$ = NewFileName$ + "E" 'QB64 doesn't include the correct character
        Case E__DIAERESIS$:          NewFileName$ = NewFileName$ + "E" 'QB64 doesn't include the correct character
        Case N__TILDE$:              NewFileName$ = NewFileName$ + "¥"
        Case O__GRAVE$:              NewFileName$ = NewFileName$ + "O" 'QB64 doesn't include the correct character
        Case O__ACUTE$:              NewFileName$ = NewFileName$ + "O" 'QB64 doesn't include the correct character
        Case O__CIRCUMFLEX$:          NewFileName$ = NewFileName$ + "O" 'QB64 doesn't include the correct character
        Case O__TILDE$:              NewFileName$ = NewFileName$ + "O" 'QB64 doesn't include the correct character
        Case O__DIAERESIS$:          NewFileName$ = NewFileName$ + "™"
        Case O__SLASH$:              NewFileName$ = NewFileName$ + "O" 'QB64 doesn't include the correct character
        Case U__GRAVE$:              NewFileName$ = NewFileName$ + "U" 'QB64 doesn't include the correct character
        Case U__ACUTE$:              NewFileName$ = NewFileName$ + "U" 'QB64 doesn't include the correct character
        Case U__CIRCUMFLEX$:          NewFileName$ = NewFileName$ + "U" 'QB64 doesn't include the correct character
        Case U__DIAERESIS$:          NewFileName$ = NewFileName$ + "š"
        Case Y__ACUTE$:              NewFileName$ = NewFileName$ + "Y" 'QB64 doesn't include the correct character
        Case Y__DIAERESIS$:          NewFileName$ = NewFileName$ + "Y" 'QB64 doesn't include the correct character
        Case Else:
            Color COLOR_WARNING : Print "Unsupported extended ASCII character: ";
            Color 15 : Print CurrentChar$;
            Color 7 : Print " (ASCII value: " + LTrim$ (Str$ (Asc (CurrentChar$))) + ")"
            Color COLOR_FILENAME
            NewFileName$ = NewFileName$ + CurrentChar$
      End Select
  Next

  FormatFileName_Screen$ = NewFileName$

End Function 'FormatFileName_Screen$



Function ShortenFolderName$ (X_FolderName$)

  FolderName$ = X_FolderName$

  If (Right$ (FolderName$, 1) = "\") Then
      FolderName$ = Left$ (FolderName$, Len (FolderName$) - 1)
  End If

  I = _InStrRev (Left$ (FolderName$, Len (FolderName$) - 1), "\")

  If I = 0 Then
      NewFolderName$ = FolderName$
  Else
      NewFolderName$ = Right$ (FolderName$, Len (FolderName$) - I)
  End If

  ShortenFolderName$ = NewFolderName$

End Function 'ShortenFolderName$



Function Commatize$ (X_N~&&)

  N~&& = X_N~&&

  N$ = LTrim$ (Str$ (N~&&))

  Result$ = NULL$
  NDigits = 0

  For I = Len (N$) To 1 Step -1
      Result$ = Mid$ (N$, I, 1) + Result$
      NDigits = NDigits + 1
      If (NDigits = 3 And I > 1) Then
        Result$ = "," + Result$
        NDigits = 0
      End If
  Next

  Commatize$ = Result$

End Function 'Commatize$



Function InputNumber (X_Prompt$, X_DefaultValue, X_MaxValue, X_PromptColor)

  'Returns -1 if the ESC key is pressed

  Prompt$      = X_Prompt$
  DefaultValue = X_DefaultValue
  MaxValue    = X_MaxValue
  PromptColor  = X_PromptColor
  AnswerColor  = X_AnswerColor

  MaxNSpaces = 2 'Includes one space for the underscore character
  If (MaxValue >= 10) Then MaxNSpaces = 3
  If (MaxValue >= 100) Then MaxNSpaces = 4
  If (MaxValue >= 1000) Then MaxNSpaces = 5
  If (MaxValue >= 10000) Then MaxNSpaces = 6

  Answer$ = LTrim$ (Str$ (DefaultValue))

  Color PromptColor : Print Prompt$;

  CursorX = Pos (0)
  CursorY = CsrLin

  Color 7 : Print Answer$;
  Color 15 : Print "_";

  KeyPressed = FALSE
  DoneTyping = FALSE
  Escape    = FALSE

  Do

      Do
        _Limit 20
        KeyPress$ = InKey$
      Loop Until (KeyPress$ <> NULL$)

      UpdateDisplay = FALSE

      If (KeyPress$ = ESC$) Then
        Answer$ = NULL$
        Escape = TRUE
        UpdateDisplay = TRUE
        DoneTyping = TRUE
      End If

      If (KeyPress$ = CR$) Then
        UpdateDisplay = TRUE
        DoneTyping = TRUE
      End If

      If (KeyPress$ = BACKSPACE$) Then
        L = Len (Answer$)
        If (L > 1) Then
            Answer$ = Left$ (Answer$, L - 1)
        Else
            Answer$ = "0"
        End If
        KeyPressed = TRUE
        UpdateDisplay = TRUE
      End If

      If (KeyPress$ >= "0" And KeyPress$ <= "9") Then
        If (Not KeyPressed) Then
            NewAnswer$ = KeyPress$
            NewAnswer~& = Val (NewAnswer$)
            If (NewAnswer~& <= MaxValue) Then
              Answer$ = NewAnswer$
              KeyPressed = TRUE
            End If
        Else
            NewAnswer$ = Answer$ + KeyPress$
            NewAnswer~& = Val (NewAnswer$)
            If (NewAnswer~& <= MaxValue) Then
              If (Answer$ = "0") Then
                  Answer$ = KeyPress$
              Else
                  Answer$ = NewAnswer$
              End If
            End If
        End If
        UpdateDisplay = TRUE
      End If

      If (UpdateDisplay) Then
        Locate CursorY, CursorX
        If (Answer$ = "0") Then
            Color 7
        Else
            Color 15
        End If
        Print Answer$;
        If (Not DoneTyping) Then
            Color 15 : Print "_";
            NSpacesLeft = MaxNSpaces - Len (Answer$) - 1
        Else
            NSpacesLeft = MaxNSpaces - Len (Answer$)
        End If
        Print Space$ (NSpacesLeft);
      End If

  Loop Until DoneTyping

  If (Escape) Then Answer$ = "-1"

  Print

  InputNumber = Val (Answer$)

End Function 'InputNumber

'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
'* *                                                                                                                * *
'* *                                                                                                                * *
'* *  PCG-32 RANDOM NUMBER GENERATOR                                                                                * *
'* *                                                                                                                * *
'* *                                                                                                                * *
'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
'* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *

Function MonthlyTimer~&

  MonthlyTimer~& = Val (Mid$ (Date$, 4, 2)) * 86400000 + Timer (.001) * 1000

End Function 'MonthlyTimer~&



Sub PCG32_Seed (Seed~&)

  'Initialize with seed

  PCG_State~&& = 0
  PCG_Inc~&& = (Seed~& * 2) Or 1
  Dummy& = PCG32_Random&
  PCG_State~&& = PCG_State~&& + &H853C49E6748FEA9B
  Dummy& = PCG32_Random&

End Sub 'PCG32_Seed



Function PCG32_Random& ()

  'Returns a 32-bit random integer

  Dim OldState~&&
  Dim Shift18~&&
  Dim Shift59~&&
  Dim XorShifted~&
  Dim Result~&
  Dim Right_Part~&
  Dim Left_Part~&
  Dim Rotate_Amount~&

  OldState~&&    = PCG_State~&&
  PCG_State~&&    = OldState~&& * &H5851F42D4C957F2D + PCG_Inc~&&
  Shift18~&&      = OldState~&& \ 262144
  XorShifted~&    = (Shift18~&& Xor OldState~&&) \ 134217728
  Shift59~&&      = OldState~&& \ 576460752303423488#
  Rotate_Amount~& = Shift59~&& And 31
  Right_Part~&    = XorShifted~& \ (2 ^ Rotate_Amount~&)
  Left_Part~&    = (XorShifted~& * (2 ^ (32 - Rotate_Amount~&))) And &HFFFFFFFF
  Result~&        = Right_Part~& Or Left_Part~&

  PCG32_Random& = Result~&

End Function 'PCG32_Random&



Function PCG32_RandomFloat! ()

  'Returns a random float in [0, 1) range
  'The Result~& can be exactly 0, but always less than 1

  PCG32_RandomFloat! = (PCG32_Random& And &HFFFFFF) / 16777216.0

End Function 'PCG32_RandomFloat!

Print this item