• Welcome to RetroCoders Community.
 

News:

Welcome to RetroCoders Community

Main Menu

Recent posts

#21
FreeBasic Game Dev / Re: Coin Hunt - Freebasic Comp...
Last post by __blackjack__ - Aug 02, 2025, 09:30 PM
On one hand its not very likely someone plays up to level 98, but then the somewhat arbitrary number of 500 array elements for coins are not enough.  Still it would be better to use dynamic arrays and just `ReDim` them to the actual needed size.

`CoinCount` and `MonsterCount` can then be removed/replaced by `UBound(...)`.

The `Goto`s when creating coins and monsters can be replaced by loops.  The other `Goto`s can be replaced by `Exit Sub`s if the mainloop is moved to a `Sub`.

Coins, monsters, and the player are represented by X and Y coordinates and all game pieces share some functionality, so it would make sense to introduce a `Type` for the position und some functions to operate on/with such positions.  It spares some repeated code fragments and function/sub names make the code easier to understand.

Some colors and characters are better defined as constants.

Const CoinColor = 14, MonsterColor = 12, PlayerColor = 15
Const CoinCharacter = chr(248), MonsterCharacter = chr(234)
Const PlayerCharacter = chr(153)

Type TPosition
    Dim X as byte
    Dim Y as byte
End Type

Declare Function IsValid (position as Const TPosition) as boolean
Declare Sub Invalidate (position as TPosition)
Declare Function IsAtSamePosition (a as Const TPosition, b as Const TPosition) as boolean
Declare Function CheckCollisions (position as Const TPosition, others() as TPosition, doInvalidate as boolean) as integer
Declare Sub SetRandom (position as TPosition)
Declare Sub MoveLeft (position as TPosition)
Declare Sub MoveRight (position as TPosition)
Declare Sub MoveUp (position as TPosition)
Declare Sub MoveDown (position as TPosition)
Declare Sub PrintAt (position as Const TPosition, text as Const String)

Declare Sub PlayLevel (ByRef score as integer, ByRef level as integer, ByRef lives as byte)
Declare Sub UpdateScore (score as integer, level as integer, lives as integer)

Dim Score as integer
Dim Level as integer
Dim Lives as byte

Randomize Timer
Screen 19

' Welcome Screen
Cls
Color 14
Locate 17, 35
Print "---- Welcome to Coin Hunt! ---"
Locate 18, 35
Print "      MagicalWizzy (2022)"
Locate 20, 20
Print "Collect all the coins without being attacked by the monsters :)"
Locate 21, 35
Print "    Press any key to begin."
Sleep

' First Start Variables
Score = 0
Level = 1
Lives = 3
Do
    PlayLevel Score, Level, Lives
Loop


Private Function IsValid (position as Const TPosition) as boolean
    IsValid = position.X <> 0
End Function

Private Sub Invalidate (position as TPosition)
    position.X = 0
End Sub

Private Function IsAtSamePosition (a as Const TPosition, b as Const TPosition) as boolean
    IsAtSamePosition = a.X = b.X AndAlso a.Y = b.Y
End Function

Private Function CheckCollisions (position as Const TPosition, others() as TPosition, doInvalidate as boolean) as integer
    Dim count as integer = 0, i as integer
    For i = 1 to UBound(others)
        If IsAtSamePosition(position, others(i)) Then
            count = count + 1
            If doInvalidate Then Invalidate(others(i))
        End If
    Next
    CheckCollisions = count
End Function

Private Sub SetRandom (position as TPosition)
    position.X = Int(Rnd * 98) + 2: position.Y = Int(Rnd * 35) + 2
End Sub

Private Sub MoveLeft (position as TPosition)
    position.X = position.X - 1
    If position.X <= 2 Then position.X = 2
End Sub

Private Sub MoveRight (position as TPosition)
    position.X = position.X + 1
    If position.X >= 99 Then position.X = 99
End Sub

Private Sub MoveUp (position as TPosition)
    position.Y = position.Y - 1
    If position.Y <= 2 Then position.Y = 2
End Sub

Private Sub MoveDown (position as TPosition)
    position.Y = position.Y + 1
    If position.Y >= 36 Then position.Y = 36
End Sub

Private Sub PrintAt (position as Const TPosition, text as Const String)
    If IsValid(position) Then Locate position.Y, position.X: Print text;
End Sub


Sub PlayLevel (ByRef score as integer, ByRef level as integer, ByRef lives as byte)
    Dim isOddCycle as boolean
    Dim i as integer, coinsCollected as integer = 0
    Dim player as TPosition = (51, 18)
    Dim coins(20 + (level - 1) * 5) as TPosition
    Dim monsters(10 + (level - 1) * 5) as TPosition

    ' Draw Border
    Cls
    Color 9
    Print chr(201);: For i=1 to 98: Print chr(205);: Next: Print chr(187);
    For i = 2 to 36
        Locate i, 1: Print Chr(186)
        Locate i, 100: Print Chr(186);
    Next
    Print chr(200);: For i=1 to 98: Print chr(205);: Next: Print chr(188);
    Color 15
    Locate 1, 36: Print " Coin Hunt! by MagicalWizzy "
    UpdateScore score, level, lives

    ' Initialise and draw Coins and Monsters
    For i = 1 to UBound(coins)
        Do
            SetRandom coins(i)
        Loop While IsAtSamePosition(coins(i), player)
    Next
    For i = 1 to UBound(monsters)
        Do
            SetRandom monsters(i)
        ' Check if Monster is too close to player.
        Loop Until Abs(monsters(i).Y - player.Y) > 4 _
                  And Abs(monsters(i).X - player.X) > 4
    Next

    ' Draw Coins and Monsters
    Color CoinColor
    For i = 1 to UBound(coins)
        PrintAt coins(i), CoinCharacter
    Next
    Color MonsterColor
    For i = 1 to UBound(monsters)
        PrintAt monsters(i), MonsterCharacter
    Next

    ' Draw Player
    Color PlayerColor
    PrintAt player, PlayerCharacter

    ' Main Game Loop
    Do
        ' Check if Player moves...
        If MultiKey(1) Then End  ' Escape pressed, close game

        isOddCycle = Not isOddCycle
        If isOddCycle Then
            Color PlayerColor
            PrintAt player, " "
            If MultiKey(77) Then MoveRight Player
            If MultiKey(75) Then MoveLeft player
            If MultiKey(72) Then MoveUp player
            If MultiKey(80) Then MoveDown player
            PrintAt player, PlayerCharacter
        End If

        ' Check if monster eats player
        If CheckCollisions(player, monsters(), False) <> 0 Then
            Locate 17, 32
            Color 11
            Print "A monster just ate you for breakfast!"
            Beep
            Sleep 5000, 1
            Beep
            lives = lives - 1
            If lives = 0 Then End
            Exit Sub
        End If
        
        ' Check if Player found coins.
        i = CheckCollisions(player, coins(), True)
        If i > 0 Then
            coinsCollected = coinsCollected + i
            score = score + i
            UpdateScore score, level, lives
        End If
        
        ' Problems with coins disappearing - redrawing all coins
        Color CoinColor
        For i = 1 to UBound(coins)
            PrintAt coins(i), CoinCharacter
        Next
        
        ' Move monsters about.
        For i = 1 to UBound(monsters)
            Color CoinColor
            PrintAt monsters(i), _
                    IIf(CheckCollisions(monsters(i), coins(), False) <> 0, _
                        CoinCharacter, " ")
            Select Case int(rnd*6)+1
                Case 1: MoveLeft monsters(i)
                Case 2: MoveRight monsters(i)
                Case 3: MoveUp monsters(i)
                Case 4: MoveDown monsters(i)
                Case Else  ' Monster stays still
            End Select
            Color MonsterColor
            PrintAt monsters(i), MonsterCharacter
        Next

        ' Check if monster eats player
        If CheckCollisions(player, monsters(), False) <> 0 Then
            Locate 17, 32
            Color 11
            Print "A monster just ate you for breakfast!"
            Beep
            Sleep 5000, 1
            Beep
            lives = lives - 1
            If lives = 0 Then
                Cls
                Print "You got"; score; " coins. Well done. You did well. Game over."
                Sleep 5000, 1
                end
            End If
            Exit Sub
        End If
        
        If coinsCollected >= UBound(coins) Then
            Locate 17,28
            Color 11
            Print "Great Job - You got all the coins! Level up."
            Beep
            Sleep 3000, 1
            Beep
            level = level + 1
            Exit Sub
        End If
        Sleep 70, 1
    Loop
    Beep: Sleep: End
End Sub

Private Sub UpdateScore (score as integer, level as integer, lives as integer)
    Dim i as byte
    Color 9
    Locate 37, 1
    Print chr(200);: For i=1 to 98: Print chr(205);: Next: Print chr(188);
    Color 11
    Locate 37, 3
    Print " Score:"; score; " ";
    Locate 37, 46
    Print " Level:"; level; " ";
    Locate 37, 89
    Print " Lives:"; lives; " ";
End Sub

It's still almost QBasic compatible, i.e. it uses almost no FreeBASIC-specific features that can't be removed easily.  No operator overloading for instance, which would really make sense for the `=` operator for instance.  Or methods on the position type.
#22
FreeBasic / Re: one of my first programs i...
Last post by __blackjack__ - Jul 25, 2025, 05:30 PM
Bit masks...  ;)
#23
FreeBasic / Re: Battle Simulation
Last post by __blackjack__ - Jul 25, 2025, 03:17 PM
The `turn` variable has no effect, because the IF tests are always true.  I guess that's a bug.
#24
FreeBasic / Re: mishka's clock
Last post by __blackjack__ - Jul 25, 2025, 03:06 PM
There's a bug in the clock program: hour, minute, and sec are defined by three independent calls to TIME$.  But time goes by between those calls and there's a chance that the values don't refer to the same point in time.  For instance if the time at the beginning of the first call is 05:59:59.9999 the result from the calls might be 5, 0, and 0.

But that's simple to fix:
      t$ = TIME$
      hour = VAL(LEFT$(t$, 2))
      minute = VAL(MID$(t$, 4, 2))
      sec = VAL(RIGHT$(t$, 2))
#25
FreeBasic / Re: Conway's game of life
Last post by __blackjack__ - Jul 25, 2025, 02:30 PM
@Tomaaz: Well that's cheating.  You are simply storing two arrays in one array.  At the expense of code readability.  And that's even unnecessary because you could have used one array with three dimensions.  One dimension each for X, Y, and "generation" for two generations.  That would make it possible to make the program even more efficient. Instead of copying the new generation to the old one, it would be possible just to switch the generation index then.
#26
FreeBasic / Re: Dancer Demo
Last post by __blackjack__ - Jul 23, 2025, 08:47 PM
There is an array out of bounds error with `Cor`.  Initialising this with -1 leads to `Cor` being 15 and then this value being used as index into the just 9 element long `CP_Cores` array.
#27
FreeBasic / Re: FreeBasic Console Game - A...
Last post by __blackjack__ - Jul 23, 2025, 04:44 PM
`SLEEP` is a problem here as it waits for a key press but doesn't clear the keyboard buffer, so whatever you press will show up at the `INPUT` later.  FreeBASIC has `GetKey` for waiting and removing the key from the buffer.

`over` has the exact opposite meaning as one would expect here.  It would also be cleaner not to set a global flag but to return the status from `game_turn`.

The five `CASE 1`, `CASE 2`, ..., `CASE 5` with always the same code can be merged into one `CASE 1 TO 5`.  The other `SELECT CASE` construct and the happy and sad variables can be replaced by arrays with the reaction text and an array to count how often which result was randomly chosen.

Then the main loop is simple enough to get rid of the subroutines:
enum TResult
    SUCCESS, NEUTRAL, FAILED, MAX = FAILED
end enum

dim reactions(TResult.MAX) as string = { "is happy", "is apathetic", "cries" }
dim stats(TResult.MAX) as integer, choice as integer, result as TResult

Randomize
do
    Cls
    print "It's a new day in 1988."
    print "Options:"
    print "1. Feed Avishai"
    print "2. Give him medications"
    print "3. Take him to the public garden"
    print "4. Play music for him"
    print "5. Put Avishai to bed"
    print "6. Exit the game"
    input choice

    Cls
    select case choice
        case 1 to 5
            result = Cast(TResult, Int(Rnd * (TResult.MAX + 1)))
            print "Avishai "; reactions(result); "."
            stats(result) += 1
        case 6
            print "Exiting the game."
            GetKey
            exit do
        case else
            print "Invalid choice. Try again."
    end select
    GetKey
loop

locate 20, 1
print "Avishai was happy"; stats(SUCCESS); " times."
print "Avishai was sad"; stats(FAILED); " times."
GetKey
#28
FreeBasic / Re: super simple Matrix Rain s...
Last post by __blackjack__ - Jul 23, 2025, 02:08 PM
`q` doesn't make any sense here.  Even in QBasic on an old DOS machine this would just cause a non noticeable delay.

`A`, `B`, and `x` are not really needed either.  All three are just used once, so we could just use the expressions used to define their values instead of the variables.

Instead of hard coding the limits of the random numbers for row and column it would be more flexible to query the number of rows and columns for the active screen mode.  Then it would also be possible to centre the message on the screen.

'THIS IS A MATRIX PROGRAM, BY JOHN. W. SZCZEPANIAK
' converted from qb to fb by ron77 2023-08-25
RANDOMIZE: SCREEN 19

DIM i AS LONG, rows AS INTEGER, columns AS INTEGER, text AS STRING

i = WIDTH: rows = HIWORD(i): columns = LOWORD(i)

COLOR 2  ' Green
DO
    LOCATE INT(RND * (rows - 1)) + 1, INT(RND * (columns - 1)) + 1
    PRINT CHR(INT(RND * 227) + 28)
    SLEEP 5  ' about 200 characters per second.
LOOP UNTIL INKEY <> ""  ' = CHR(27)

CLS
text = "THE MATRIX HAS YOU"
LOCATE rows \ 2, (columns - LEN(text)) \ 2
PRINT text
SLEEP

#29
FreeBasic / Re: myStar what ?
Last post by __blackjack__ - Jul 23, 2025, 09:05 AM
Retyped and made backwards compatible with QBasic:
DECLARE SUB star (x AS INTEGER, y AS INTEGER, n AS INTEGER, r AS INTEGER)
CONST PI = 3.141592653589793#
RANDOMIZE TIMER: SCREEN 12: star 200, 200, 18, 150: SLEEP

SUB star (x AS INTEGER, y AS INTEGER, n AS INTEGER, r AS INTEGER)
  DIM px(n) AS INTEGER, py(n) AS INTEGER, i AS INTEGER, j AS INTEGER
  DIM col AS INTEGER, ad AS DOUBLE, a AS DOUBLE
  ad = 2 * PI / n
  FOR i = 1 TO n
    a = (i - 1) * ad
    px(i) = x + INT(r * COS(a)): py(i) = y + INT(r * SIN(a))
  NEXT
  col = 1 + INT(15 * RND)
  FOR i = 1 TO n
    FOR j = i + 1 TO n
      LINE (px(i), py(i))-(px(j), py(j)), col
    NEXT
  NEXT
END SUB
#30
General Discussion / Re: what kind of music do you ...
Last post by __blackjack__ - Jul 22, 2025, 06:17 PM
My favourite genres are Rock, Punk, Metal.  And music from demos and games, mainly from the C64, so I guess that genre is "Chiptunes".

Out of curiosity what the genre tags on the audio file collection on my computer have to say I've wrote a FreeBASIC program that counts the different genres of those files:
#include Once "dir.bi"
#include Once "glib.bi"

Const DirAttribMask = fbNormal Or fbDirectory Or fbHidden

Function DirExists(ByVal path As String) As Boolean
    Dim attrib As Integer
    
    path = Dir(RTrim(path, "/"), DirAttribMask, attrib)
    DirExists = path <> "" And (attrib And fbDirectory) <> 0
End Function

'Declare the stuff we need from libtag_c / TAG_C.DLL right here.
#inclib "tag_c"
Type Taglib_File As Any Ptr
Type Taglib_Tag As Any Ptr

Extern "C"
    Declare Function taglib_file_new(filename As Const ZString Ptr) _
        As Taglib_File
    Declare Sub taglib_file_free(file as TagLib_File)
    Declare Function taglib_file_is_valid(file As Const TagLib_File) As Boolean
    Declare Function taglib_file_tag(file As Const TagLib_File) As Taglib_Tag

    Declare Function taglib_tag_genre(tag As Const Taglib_Tag) As ZString Ptr
    Declare Sub taglib_tag_free_strings
End Extern

'Open an audio file.  Returns 0 if the file isn't a valid audio file
'understood by the TagLib library.
Function OpenFile(filename As Const String) As Taglib_File
    Dim file As Taglib_File
    
    file = taglib_file_new(filename)
    If file AndAlso taglib_file_is_valid(file) = 0 Then
        taglib_file_free file
        file = 0
    End If
    OpenFile = file
End Function

'Get the genre from the audio file.
Function GetGenre(file As Const Taglib_File) As String
    GetGenre = *taglib_tag_genre(taglib_file_tag(file))
    taglib_tag_free_strings
End Function


Type TItem
    genre As ZString Ptr  'Genre name.
    count As Integer      'Number of files with that genre.
    
    Declare Destructor()
End Type

Destructor TItem()
    g_free(genre)
End Destructor

'Compare by count (descending) and genre name (ascending).
Function CompareItem(a As Const Any Ptr, b As Const Any Ptr, _
                    userData As Any Ptr) As gint
    Dim itemA As Const TItem Ptr, itemB As Const TItem Ptr, result As Integer
    
    itemA = a: itemB = b: result = itemB->count - itemA->count
    If result = 0 Then result = g_strcmp0(itemA->genre, itemB->genre)
    CompareItem = result
End Function


'Count the given key.
Sub UpdateCounts(counts As GHashTable Ptr, key As String)
    Dim keyPtr As ZString Ptr, value As Any Ptr
    
    keyPtr = StrPtr(key): If keyPtr = 0 Then keyPtr = @""
    If g_hash_table_contains(counts, keyPtr) Then
        value = g_hash_table_lookup(counts, keyPtr)
    Else
        value = 0: keyPtr = g_strdup(keyPtr)
    End If
    g_hash_table_insert(counts, keyPtr, value + 1)
End Sub

'Move the data from the hash table into an array and destroy the hash table.
Sub MoveCountsToArray(counts As GHashTable Ptr, result() As TItem)
    Dim i As Integer
    Dim iter As GHashTableIter, key As ZString Ptr, value As Any Ptr
    
    If g_hash_table_size(counts) > 0 Then
        Redim result(g_hash_table_size(counts) - 1)

        i = 0
        g_hash_table_iter_init(@iter, counts)
        Do While g_hash_table_iter_next(@iter, @key, @value)
            With result(i)
                .genre = key: .count = CInt(value)
            End With
            i = i + 1
        Loop
    End If
    g_hash_table_destroy(counts)
End Sub


'Process the directories starting with given path recursively and populate the
'genre counter hash table.
Sub ProcessDirs(path As Const String, counts As GHashTable Ptr)
    Dim i As Integer
    Dim subdirs(Any) As String, filename As String, attrib As Integer
    Dim file As Taglib_File
    
    filename = Dir(path + "*", DirAttribMask, attrib)
    Do Until filename = ""
        If filename <> "." AndAlso filename <> ".." Then
            If attrib And fbDirectory Then
                i = UBound(subdirs) + 1
                Redim Preserve subdirs(i)
                subdirs(i) = filename
            Else
                file = OpenFile(path + filename)
                If file Then
                    UpdateCounts counts, Trim(GetGenre(file))
                    taglib_file_free file
                End If
            End If
        End If
        filename = Dir(attrib)
    Loop
    If UBound(subdirs) <> -1 Then
        For i = 0 To UBound(subdirs)
            ProcessDirs path + subdirs(i) + "/", counts
        Next
    End If
End Sub


Dim path As String, i As Integer, counts As GHashTable Ptr, items(Any) As TItem

path = Command$
If path = "" Then Print "No path given.": End
If Not DirExists(path) Then Print "'"; path; "' does not exist": End
If Right$(path, 1) <> "/" Then path = path + "/"

counts = g_hash_table_new(@g_str_hash, @g_str_equal)
ProcessDirs path, counts
MoveCountsToArray counts, items()
If UBound(items) <> -1 Then
    g_qsort_with_data(@items(0), UBound(items) + 1, SizeOf(TypeOf(items)), _
                      @CompareItem, 0)
    For i = 0 To UBound(items)
        With items(i)
            Print Using "####### "; .count;: Print *.genre
        End With
    Next
End If
Output:
  2346 Rock
  1538 Punk Rock
    877 
    689 Metal
    676 Punk
    558 Pop
    460 Alternative
    443 Soundtrack
    377 Ska
    338 Hard Rock
    338 Progressive Rock
    333 Thrash Metal
    297 Heavy Metal
    239 Crossover
    168 Reggae
    148 Alternative Rock
    144 Grunge
    144 Hardcore
    111 Psychobilly
    106 Alternative & Punk
    103 Experimental Rock
    102 Rock - Alternative/Funk
    93 Other
    83 Pop Rock
    78 Indie
    74 Electronic
    74 Latin
    70 Hip-Hop
    70 Jazz
    61 Classical
    60 Comedy
    58 Southern Rock
    53 Pop/Rock
    49 Classic Rock
    48 Country
    47 Punkrock
    46 Rockabilly
    45 Acid Jazz
    45 Classic
    43 Blues
    41 Acoustic
    40 Irish Folk / Punk Rock
    38 Altern Rock
    37 Hip Hop/Rap
    36 Stoner Rock
    35 Instrumental
    34 Nu Metal
    34 Rock/Pop
    33 Rock & Roll
    30 Swing
    28 Garage Rock
    28 Punk/Ska
    28 Unknown
    27 Celtic Punk
    26 Alternative Country
    25 Chanson
    24 Alternative Metal
    24 Industrial Metal
    24 Soul
    22 Punk Rock / Ska
    22 Rock / Pop
    18 BritPop
    18 Pop / Rock / Ska
    18 Satire
    17 Irish Punk
    17 R&B
    16 Darkwave
    16 Folk
    16 Indie Rock
    16 Irish Folk Punk
    16 Post Grunge
    16 Punk Rock/Irish Folk
    16 Solo Piano
    16 Sonstiges
    15 Cello Metal
    15 Christmas
    15 Funk Metal
    15 Indiecountry
    15 Rap Metal
    14 Blues Rock
    14 Gothic
    14 Grunge / Metal
    14 Hardcore Punk
    13 Christmas Punk
    13 Classical Metal
    13 Death Metal
    13 Deathpunk
    13 Experimental Metal
    13 Funk
    13 Glamrock
    13 Horror Punk
    13 Psychedelic Rock
    13 Rap Rock
    13 Street Punk
    12 8Bit
    12 Ambient
    12 General Alternative Rock
    12 Metal+Latin
    12 Symphonic metal
    12 Unbekannt
    11 Bachata
    11 Otros
    10 Electronica
    10 Gothic Rock
    10 Heavy Metal/Hard Rock
    10 Trip-Hop
      9 Trance
      9 genre
      8 Chamber Music
      8 Cult
      8 Latin Rock
      5 default
      4 Oldies
      4 World
      3 Bass
      3 Big Beat
      3 Talk Radio
      2 Abstract
      2 Ballad
      2 Garage
      2 Hiphop
      2 Noise
      2 Skatepunk
      1 Chillout
      1 Disco
      1 Drum & Bass
      1 Folk/Rock
      1 Gangsta
      1 Gospel & Religious
      1 Industrial
      1 Native American
      1 Neu
      1 New Wave
      1 Noise-Rock
      1 Podcast
      1 Singer-Songwriter
      1 Top 40
      1 Unclassifiable
      1 rock
      1 world
SMF spam blocked by CleanTalk