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

Username/Email:
  

Password
  





Search Forums

(Advanced Search)

Forum Statistics
» Members: 496
» Latest member: braveparrot
» Forum threads: 2,843
» Forum posts: 26,648

Full Statistics

Latest Threads
Big problem for me.
Forum: General Discussion
Last Post: Kernelpanic
20 minutes ago
» Replies: 8
» Views: 119
Fun with Ray Casting
Forum: a740g
Last Post: Petr
58 minutes ago
» Replies: 5
» Views: 83
1990's 3D Doom-Like Walls...
Forum: Programs
Last Post: a740g
2 hours ago
» Replies: 10
» Views: 289
QBJS v0.9.0 - Release
Forum: QBJS, BAM, and Other BASICs
Last Post: grymmjack
3 hours ago
» Replies: 16
» Views: 263
Editor WIP
Forum: bplus
Last Post: aadityap0901
Today, 08:54 AM
» Replies: 12
» Views: 677
discover graphics with xa...
Forum: Programs
Last Post: hsiangch_ong
Yesterday, 10:39 PM
» Replies: 0
» Views: 29
another variation of "10 ...
Forum: Programs
Last Post: Jack002
Yesterday, 10:05 PM
» Replies: 37
» Views: 720
Aloha from Maui guys.
Forum: General Discussion
Last Post: doppler
Yesterday, 03:32 PM
» Replies: 14
» Views: 372
Cautionary tale of open, ...
Forum: General Discussion
Last Post: doppler
Yesterday, 03:28 PM
» Replies: 0
» Views: 38
Extended KotD #22: _MOUSE...
Forum: Keyword of the Day!
Last Post: SMcNeill
Yesterday, 12:29 AM
» Replies: 0
» Views: 59

 
  _OpenFile Dialog in same folder as exe
Posted by: bplus - 12-04-2022, 08:28 PM - Forum: Utilities - Replies (16)

I want the DefaultPathAndFile$ where my exe is.

_StartDir$ sure didn't get it, that's my QB64 exe folder

_Cwd$ works if I have been in folder for awhile but sometimes seems to be wrong when just starting cold.

So I am trying this:

Code: (Select All)
Function ExePath$
    Dim As Long t1, t2
    t1 = _InStrRev(Command$(0), "\")
    t2 = _InStrRev(Command$(0), "/")
    If t1 > t2 Then ExePath$ = Left$(Command$(0), t1 - 1) Else ExePath$ = Left$(Command$(0), t2 - 1)
End Function

Look alright?  I add forward slash in case of Linux, Windows doesn't care. It seems to be working for all the tests I gave trying to get out of _CWD$

Print this item

  Day 024: LCASE$
Posted by: Pete - 12-04-2022, 05:17 AM - Forum: Keyword of the Day! - Replies (12)

Not much to say here. LCASE$ just converts any uppercase characters in a string to lowercase. LCASE$(mystring$)

The most obvious use for LCASE$ is an input routine...

Code: (Select All)
PRINT "Was this information helpful? Y/N"; ans$
DO
    _LIMIT 30
    mykey$ = INKEY$
    IF LCASE$(mykey$) = "y" THEN PRINT "You darn tootin' it waz ya mangy polecat!"
LOOP

The code above allows the user to type either "y" or "Y" to get a printed response."

Anyway, not much ELSE to see here; besides, it's Sunday, and God takes this day off. Me, I take a 1/2 day! Big Grin

Pete

Print this item

  mini-weapon mixer
Posted by: James D Jarvis - 12-03-2022, 11:20 PM - Forum: Programs - Replies (2)

A cousin to the mini-monster-mixer and robot mixer.  Generates 80 weapon sprites for a rpg/rougelike.
here's a sample run:

[Image: image.png]


Code: (Select All)
'Mini-Weapon-Mixer v0.1
'By James D. Jarvis December 2022
'This program uses BASIMAGE coded by Dav for QB64GL 1.4, MAY/2020
'
'generate a sprite sheet of weapons for use in a roguelike/RPG
'each is 64 by 64 pixels but code here can be modified to change that
'
'press c or s to save a sprite sheet to the clipboard so you can paste it into a paint program
'and save is whatever format you desire
'pres <esc> to quit
'
'License: Share sprite sheets as long as they include generated credit bar in image
'any programs using original code or graphics from source or generated by program
' please include the following (or equivalent) line somwhere in comments and documentation:
'Includes Art and/or Code from Mini-Weapon-Mixer v0.1 created by James D. Jarvis
'
Randomize Timer
Dim Shared ms&
ms& = _NewImage(512, 360, 32)
Screen ms&
_Title "Mini-Weapon-Mixer V0.1"
Dim Shared part&
Dim Shared kk1 As _Unsigned Long
Dim Shared kk2 As _Unsigned Long
Dim Shared kk3 As _Unsigned Long
Dim Shared kk4 As _Unsigned Long
Dim Shared kk5 As _Unsigned Long
Dim Shared kk6 As _Unsigned Long

Dim Shared clr~&
part& = BASIMAGE1&

Type weapon_type
    style As Integer
    pole As Integer
    rhead As Integer
    chead As Integer
    lhead As Integer
    blade As Integer
    handle As Integer
    fgard As Integer
    k1 As _Unsigned Long
    k2 As _Unsigned Long
    k3 As _Unsigned Long
    k4 As _Unsigned Long
End Type
Dim Shared weapon_limit
weapon_limit = 80
Dim klrset(12, 3) As Integer
Dim Shared wlook(weapon_limit) As weapon_type
_Source part&
'read the colors from the color reference bar whichever color is in the top left corner will be transparent
clr~& = Point(0, 0) 'find background color of image
kk1 = Point(0, 1): kk2 = Point(0, 2): kk3 = Point(0, 3): kk4 = Point(0, 4)
_Dest part&
Line (0, 0)-(0, 8), clr~& 'erase the color reference bar from the bit map
_ClearColor clr~&, ms& 'set background color as transparent
_ClearColor clr~&, part&
_Source ms&
_Dest ms&
Do
    Cls
    mmx = 0: mmy = 0
    For m = 1 To weapon_limit
        'create a new set of weapon sprites
        wlook(m).style = Int(1 + Rnd * 20)
        '1- pole only
        '2 - pole and chead
        '3 - pole chead,rhead
        '4 - pole chead,rhead and lhead
        '5- pole rhead and lhead
        '6- pole and rhead
        '7- sword and handle
        '8- sword , handle, and foregarde
        '9- sword and pole
        '10- sword, pole and foregarde
        '11- sword, pole and  lhead
        '12- sword,pole lhead and rhead
        Select Case wlook(m).style
            Case 1
                wlook(m).pole = Int(1 + Rnd * 20)
                wlook(m).rhead = 0
                wlook(m).chead = 0
                wlook(m).lhead = 0
                wlook(m).blade = 0
                wlook(m).handle = 0
                wlook(m).fgard = 0
            Case 2, 18
                wlook(m).pole = Int(1 + Rnd * 20)
                wlook(m).rhead = 0
                wlook(m).chead = Int(1 + Rnd * 20)
                wlook(m).lhead = 0
                wlook(m).blade = 0
                wlook(m).handle = 0
                wlook(m).fgard = 0
                wlook(m).style = 2

            Case 3, 13, 19
                wlook(m).pole = Int(1 + Rnd * 20)
                wlook(m).rhead = Int(1 + Rnd * 20)
                wlook(m).chead = Int(1 + Rnd * 20)
                wlook(m).lhead = 0
                wlook(m).blade = 0
                wlook(m).handle = 0
                wlook(m).fgard = 0
                wlook(m).style = 3

            Case 4
                wlook(m).pole = Int(1 + Rnd * 20)
                wlook(m).rhead = Int(1 + Rnd * 20)
                wlook(m).chead = Int(1 + Rnd * 20)
                wlook(m).lhead = Int(1 + Rnd * 20)
                wlook(m).blade = 0
                wlook(m).handle = 0
                wlook(m).fgard = 0

            Case 5, 20
                wlook(m).pole = Int(1 + Rnd * 20)
                wlook(m).rhead = Int(1 + Rnd * 20)
                wlook(m).chead = 0
                wlook(m).lhead = Int(1 + Rnd * 20)
                wlook(m).handle = 0
                wlook(m).fgard = 0
                wlook(m).style = 5

            Case 6, 14
                wlook(m).pole = Int(1 + Rnd * 20)
                wlook(m).rhead = Int(1 + Rnd * 20)
                wlook(m).chead = 0
                wlook(m).lhead = 0
                wlook(m).blade = 0
                wlook(m).handle = 0
                wlook(m).fgard = 0
                wlook(m).style = 6

            Case 7, 15, 16
                wlook(m).pole = 0
                wlook(m).rhead = 0
                wlook(m).chead = 0
                wlook(m).lhead = 0
                wlook(m).blade = Int(1 + Rnd * 20)
                wlook(m).handle = Int(1 + Rnd * 10)
                wlook(m).fgard = 0
                wlook(m).style = 7

            Case 8
                wlook(m).pole = 0
                wlook(m).rhead = 0
                wlook(m).chead = 0
                wlook(m).lhead = 0
                wlook(m).blade = Int(1 + Rnd * 20)
                wlook(m).handle = Int(1 + Rnd * 10)
                wlook(m).fgard = Int(1 + Rnd * 10)

            Case 9, 17
                wlook(m).pole = Int(1 + Rnd * 20)
                wlook(m).rhead = 0
                wlook(m).chead = 0
                wlook(m).lhead = 0
                wlook(m).blade = Int(1 + Rnd * 20)
                wlook(m).handle = 0
                wlook(m).fgard = 0
                wlook(m).style = 9

            Case 10
                wlook(m).pole = Int(1 + Rnd * 20)
                wlook(m).rhead = 0
                wlook(m).chead = 0
                wlook(m).lhead = 0
                wlook(m).blade = Int(3 + Rnd * 18)
                wlook(m).handle = 0
                wlook(m).fgard = Int(1 + Rnd * 10)
            Case 11
                wlook(m).pole = Int(1 + Rnd * 20)
                wlook(m).rhead = 0
                wlook(m).chead = 0
                wlook(m).lhead = Int(1 + Rnd * 15)
                wlook(m).blade = Int(1 + Rnd * 20)
                wlook(m).handle = 0
                wlook(m).fgard = Int(1 + Rnd * 10)
            Case 12
                wlook(m).pole = Int(1 + Rnd * 20)
                wlook(m).rhead = Int(1 + Rnd * 15)
                wlook(m).chead = 0
                wlook(m).lhead = Int(1 + Rnd * 15)
                wlook(m).blade = Int(1 + Rnd * 20)
                wlook(m).handle = 0
                wlook(m).fgard = Int(1 + Rnd * 10)
        End Select

        wlook(m).k1 = _RGB32(Int(80 + Rnd * 150), Int(80 + Rnd * 150), Int(80 + Rnd * 150))
        If Rnd * 6 < 2 Then wlook(m).k1 = _RGB32(Int(80 + Rnd * 150), Int(80 + Rnd * 120), Int(80 + Rnd * 50))
        kk = Int(155 + Rnd * 100)
        vk1 = kk: vk2 = kk: vk3 = kk
        Select Case Int(Rnd * 10)
            Case 1
                vk1 = Int(vk1 * .8)
                vk2 = Int(vk2 * .9)
            Case 2
                vk3 = Int(vk3 * .8)
                vk2 = Int(vk2 * .9)
            Case 3
                vk1 = Int(vk1 * .8)
                vk3 = Int(vk3 * .9)
        End Select
        wlook(m).k2 = _RGB32(vk1, vk2, vk3)
        wlook(m).k3 = _RGB32(Int(vk1 * 0.7), vk2 * (0.7), Int(vk3 * 0.7))
        kk = Int(150 + Rnd * 100)
        wlook(m).k4 = _RGB32(kk, kk, Int(kk / Int(1 + Rnd * 10)))

        draw_weapon mmx, mmy, m, 1
        mmx = mmx + 32
        If mmx >= _Width Then
            mmx = 0
            mmy = mmy + 64
        End If
    Next m
    md$ = "Weapon Sprite Sheet generated " + Date$ + " at " + Time$
    md2$ = "Mini-Weapon-Mixer V0.1 by James D. Jarvis"
    _PrintString (0, 321), md$
    _PrintString (0, 337), md2$
    Do
        _Limit 60
        kk$ = InKey$
    Loop Until kk$ <> ""
    If kk$ = "C" Or kk$ = "c" Or kk$ = "S" Or kk$ = "s" Then
        _ClipboardImage = ms&
        _Delay 0.3
        Locate 1, 1: Print "Sprite Sheet Saved to Clipboard"
        Sleep 3
    End If
Loop Until kk$ = Chr$(27)
_FreeImage part&
System


Sub draw_weapon (Mx, my, mid, scale)
    'generate a monster image from source sprite sheet part& and render to the programs main screen ms&
    tempi& = _NewImage(32, 64, 32)
    'tempi& creates a temporary one sprite image for rendering
    _ClearColor clr~&, tempi&
    _Dest tempi&
    Cls
    'Locate 1, 1: Print wlook(mid).style
    Select Case wlook(mid).style
        Case 1 To 6
            _PutImage (0, 32)-(31, 63), part&, tempi&, ((wlook(mid).pole - 1) * 32, 64)-((wlook(mid).pole - 1) * 32 + 31, 64 + 31)
            If wlook(mid).rhead > 0 Then _PutImage (0, 0 + 16)-(31, 31 + 16), part&, tempi&, ((wlook(mid).rhead - 1) * 32, 32)-((wlook(mid).rhead - 1) * 32 + 31, 32 + 31)
            If wlook(mid).lhead > 0 Then _PutImage (0, 0 + 16)-(31, 31 + 16), part&, tempi&, ((wlook(mid).lhead - 1) * 32, 96)-((wlook(mid).lhead - 1) * 32 + 31, 96 + 31)
            If wlook(mid).chead > 0 Then _PutImage (0, 0 + 16)-(31, 31 + 16), part&, tempi&, ((wlook(mid).chead - 1) * 32, 0)-((wlook(mid).chead - 1) * 32 + 31, 0 + 31)

        Case 7, 8
            _PutImage (0, 0 + 24)-(31, 31 + 24), part&, tempi&, ((wlook(mid).blade - 1) * 32, 128)-((wlook(mid).blade - 1) * 32 + 31, 128 + 31)
            If wlook(mid).handle > 0 Then _PutImage (0, 0 + 24)-(31, 31 + 24), part&, tempi&, ((wlook(mid).handle - 1) * 32, 160)-((wlook(mid).handle - 1) * 32 + 31, 160 + 31)
            If wlook(mid).fgard > 0 Then _PutImage (0, 0 + 24)-(31, 31 + 24), part&, tempi&, ((wlook(mid).fgard + 9) * 32, 160)-((wlook(mid).fgard + 9) * 32 + 31, 160 + 31)

        Case 9 To 12
            _PutImage (0, 32)-(31, 63), part&, tempi&, ((wlook(mid).pole - 1) * 32, 64)-((wlook(mid).pole - 1) * 32 + 31, 64 + 31)
            If wlook(mid).rhead > 0 Then _PutImage (0, 0 + 16)-(31, 31 + 16), part&, tempi&, ((wlook(mid).rhead - 1) * 32, 32)-((wlook(mid).rhead - 1) * 32 + 31, 32 + 31)
            If wlook(mid).lhead > 0 Then _PutImage (0, 0 + 16)-(31, 31 + 16), part&, tempi&, ((wlook(mid).lhead - 1) * 32, 96)-((wlook(mid).lhead - 1) * 32 + 31, 96 + 31)

            _PutImage (0, 0 + 13)-(31, 31 + 13), part&, tempi&, ((wlook(mid).blade - 1) * 32, 128)-((wlook(mid).blade - 1) * 32 + 31, 128 + 31)
            If wlook(mid).fgard > 0 Then _PutImage (0, 0 + 13)-(31, 31 + 13), part&, tempi&, ((wlook(mid).fgard + 9) * 32, 160)-((wlook(mid).fgard + 9) * 32 + 31, 160 + 31)
    End Select


    _Source tempi&
    'repaint source image with generate color values for new monster sprite
    For y = 0 To 63
        For x = 0 To 31
            Select Case Point(x, y)
                Case kk1
                    PSet (x, y), wlook(mid).k1
                Case kk2
                    PSet (x, y), wlook(mid).k2
                Case kk3
                    PSet (x, y), wlook(mid).k3
                Case kk4
                    PSet (x, y), wlook(mid).k4
            End Select
        Next x
    Next y
    'generated image in tempi& is rendered to ms& as a 32 by 64 sprite

    _PutImage (Mx, my)-(Mx + 31, my + 63), tempi&, ms&
    _Source ms&
    _Dest ms&
    _FreeImage tempi&
End Sub

'================================
'PNG file saved using BASIMAGE1&
'================================
Function BASIMAGE1& 'weapon_pieces.png
    v& = _NewImage(640, 192, 32)
    Dim m As _MEM: m = _MemImage(v&)
    A$ = ""
    A$ = A$ + "haIkM_ULLLT\504KB\9aV4SeD:=F<Ie?dH\RC`>l?LkBaiJjTIT?ZX[6TjS?"
    A$ = A$ + "nhk?000VS?o:oH00h5mmgO6O<0o]ci8nKUm^cfTGo_=gC?oCVoGO=keOh?QK"
    A$ = A$ + "WSB?6PoOhnoIKb[oMYo?m0>?km<DI?4g;[Z63Yco?Mmof9hZ6g_KLgCfJOjW"
    A$ = A$ + "o#nm0Y73Y\jm9I=8mI`>D3VVKelcjoXRnOfcIoGGeHhIoj:73EWMWTnLOjlW"
    A$ = A$ + "\d?kY3[obO^?koH_?YZ17cG?PiZmEG_k#oO[OmnoL5SQWm[OG[oMBjkMFSQd"
    A$ = A$ + "eQYjF]?mgoCjeoYWOjeodgiV^gW>d3J7639Vc;E>_gkijIonGeHh\<ClmoFM"
    A$ = A$ + "oOgK^FM=X3BOgg=CinV>:MoOYOVCjl?>6BUOjk0BOOK7VoMHOHjj#RikN?PE"
    A$ = A$ + "WoSnW[:kD_o]TiW^ng5Mh<o4[k7U^gSdmo=il_Y3iWlNPdWoCO7hTko;mn^M"
    A$ = A$ + "EWoSnW[:KmoEOfMAjknVJMo8moU9oenndW1jBnYN78Yoi^Y;eoY=6Bmm_N_a"
    A$ = A$ + "#Z\goeEUmcne_ci__fWn>XDBm^__gNQY9mmnjoCoO[cO9oiPGoOcZo_mnnB>"
    A$ = A$ + "oC]g;Io7WUMUSQdiOfh8aNPddoGFC^o[d[o[ilanOB>oCWORa#7^oEoOeG3f"
    A$ = A$ + "OVo4koCnb_3B?oCVOjkMCgoEjlC_n;olWoj`n_9ekE7bo\NoB]?HbWoTOnlC"
    A$ = A$ + ";mON_BnnOBoM?TniOYc?ij_lcVO7nngComOT>oeH8m?kMYoI`;mkO>Efb?oJ"
    A$ = A$ + "odn\OM8okmcNbdnn_T_lCn\gdOn[9GoU_lUOnlWHoGYco\O^CBg7jTZobGnM"
    A$ = A$ + "iWm[YFoU_lU_lWHnWmcORnoT_l[=Kmo9Oi;Oi;oZcEoObGnjoKZeOi;OiW?o"
    A$ = A$ + "dOocC=oSIgQa`dZobGnbGnbGnC=Obbj?0000000000000000000000000000"
    A$ = A$ + "0000000000000000000000000000lgU_onchS1POUcU0`Ej[_nWWehi=#O<m"
    A$ = A$ + "cS9VoC_V3<;7kocmO#?<mcSYjoKbeLPIiFoO;^k3jQYg;BZiod[k0canko[C"
    A$ = A$ + "gm]gKJW6KDW9^fgacSCI=OZeLPIYSmHML<eYa5e_78eW=9mn]^doGbN`l>01"
    A$ = A$ + "hjdagoWNoV]cjo_ja#jmMMhlHjg1GjnoL_3<7ClW1^TmoM<kmWi[g_gOgK=n"
    A$ = A$ + "\m0GaNQ7mWmY:fkm:iOUS1iOnj\nonej#ja1DUYe3Hb_W]WeoGjg1dEKj?Wi"
    A$ = A$ + "EGo_ZJdjOgEW[lkOnMiLIRaAWZ1MH<#>[mREWgDfc]GOkDoOELooSbm:cN_O"
    A$ = A$ + "k^]O[jgoEbno>>6>KmYRiOjgoedc_j\_GnEnIOOUl[^nWNnGY7MoGeig]iO5"
    A$ = A$ + "eRGYoSZ[oE^?XC[okonEd3hmZoEdKfnl[CWoGSWD?omNmRoWN\Xo?igUnoNf"
    A$ = A$ + "Wo9AnEFokfl_C[oC=o[\fO_l[hIoM\nOeejkUoIcaZ>;nOYo_[XgSEF?J]?m"
    A$ = A$ + "WoiZkonEGo_ji_noVMnEWmNnM[oW[>KcoO?omkR>>^BFoCWOZmOENnoh_GEm"
    A$ = A$ + "0W^o_GIlMUm0n:eo[J_`nlZbemcVo?Zg_4k?ZHLl\n_[Zo3ig_l[^o_SoN9j"
    A$ = A$ + "oi\lCdoMjioISS:Wo;We7HUiGQG9oZjoL?_:Z9o=DoOWm:goLEeWcj_Y3com"
    A$ = A$ + "J#7fKLU[1Mim?UNm_KcoYlnoNfcg^baamb_ZcoMMnOfNTdco[X?XZOO3o=TO"
    A$ = A$ + "U?c[Sco9iN_gQ:oloMMmO2k;fGc?noFe[o9Ze_bl_ZWoW>oZc^kVnk6HO?Bb"
    A$ = A$ + "a`Eeo7onJmdg_3hIOYk1Z;iWNM89??\?eQ[jk060nEY__?]dmoT>o^<6BKjc"
    A$ = A$ + "o>E7j`H00N_U^gWdigUa0mPm10`4TniMYcohhX;S500h:U^WWdigea200000"
    A$ = A$ + "000000000000000000000000l0One?58o_njWVhl?MnZoc^n;Oi;Oi;oIVod"
    A$ = A$ + "OnGjiObl_EcGogF`HogH`1GoG[1[eQ8co?bVOjcobOfiOKOObmoYc?MmGnc^"
    A$ = A$ + "oWY?oCgog^9=ok#nY_oOUojGGMnY?o;oIWOjcOYWobOfiWniOCOnW>OeOmo]"
    A$ = A$ + "f7DeHHoM>GmkQFnIcoXdgoTllgN=?I?X9WkkkoZN<L<o4eQXOo?3^oSdOoWC"
    A$ = A$ + "?oO#oOaonO?n>0[bI_ki:_7#nIco\aAUi^TnlGjioJ<TZf?mn_BWOjWoU^o_"
    A$ = A$ + "^Lgg4co7doGlnoFk5BUkTonGVDnW]7oAiGaIRYonoVdn?iOR1goWiOnlCGoC"
    A$ = A$ + "WOjno>nI`CTOjmOb_^l?kmlLFnE^WLBeo7=6BUM7joNb_oad?o;mkoKbOo[Y"
    A$ = A$ + "co7doG\mok?7nMom?Tnm?U>odeoE>_b?oQE^W8icnSOok7c]o?ioAlWoU^o_"
    A$ = A$ + "9om_V>oOHhmoMlIREWm:cejnkNoOWeWamjoh:NoCYcoD5_oo\kL^Wg]mObGn"
    A$ = A$ + "M;o^ll?co<codiW^nW>odOn_diWlim9jo8MnoV#koS?_oGYOgKYcO]FTL^Ol"
    A$ = A$ + "co<YlGS1_o_IfoaIO^o9UOjjO7b?nio?bocNdTol?Yc?mnodiW^nW>oeH8Ef"
    A$ = A$ + "Y_oKji?mcOYkocgok\OnCjcoY?o9oiNoG7b?mkO;MnYgo9oIOnOjcOi?klCg"
    A$ = A$ + "oGjiOjcoYc?MmOjiWNm?Mn[a#Z\WNn^ogm_bO^iWl?o_MHnWnlOjlCGoWNnY"
    A$ = A$ + "OoKYcoV9goEjleoWkOUoLcO]ggio0cm6TOjjodc?mj_lUoT_oIjcOi?klmio"
    A$ = A$ + "LokTgoW>od_o]diOcTOoKYcgmo^oEnc=OWoeogTgoW>od[oYcO=6BUmdcgmo"
    A$ = A$ + "^oEnc=OWo7ncO7nnodiWnm_U>odkoUo\_oIjcOi?kleoWkOWlnodiWNm?Mn["
    A$ = A$ + "a#Z\WNn^ogm_bO^kolWogQolgQOn;MnYOoKYcoV9goEjleoWkOUOfmOMhkOa"
    A$ = A$ + "io0cm6TOjjodc?mj_lUoT_oIjcOiWOoGj?o_noLokD?oU>od_o]diWNo_loi"
    A$ = A$ + "c0BTOjcOY[oYWobOfkodoGnkOMocLc?mj_lU_noLo[lmnoVJm?mm_^oI^iWn"
    A$ = A$ + "lGjmObOfiWnlgdWoC?od[ojocj_kOVKna?oma\oco`dcgkocm_C=oS[mYf3T"
    A$ = A$ + "NoWoloZo_YNn_3i[o?i[o_iNoOjioTc_;moimo=gcoYc?MmOji7olg7c^o7i"
    A$ = A$ + "7>oQOoOjioTcGoOoLN[o_0cm6TOjjodcGoObOdi?lkoC?oWNnYGoWNnFoWMm"
    A$ = A$ + "OjiWNmGnb?jm?#Hfk30000000000000000000d39okN=HBLF30BKmgkfnko7"
    A$ = A$ + "X6C_o_^<okbh0PZ]jmCoO#=BnOWSjR><o]>0<I[nnlgmm#=VNO7MHngQa00Y"
    A$ = A$ + "LlMoME_o_SokF?VcTWenkeR9G?j`l?Mn0TDEmVMF>j3L6>kcHdUen4m0T^o_"
    A$ = A$ + "dmmTNnOL<T\>0#BElnofcBoOcDgjo?E?0Yko9M_?MIn[o?HfVnOV7Z\o_Si]"
    A$ = A$ + "g7`RkUOoL_gognN]ZGogcjHnGiHhNI8onTO5koBhMOn1_:mnmJ?Gn?I]nIVl"
    A$ = A$ + "mC_c_GRWegOe[o?JlDEo7?[>T:odcoTiOUIgQcf9oI`X3cObY3OW?;MkW3YD"
    A$ = A$ + "ePmgoaEGCBOgoaa`S67YF;ZlmOTHne]nolnoToIiOEinXkJZZggDigQioSDM"
    A$ = A$ + "nMkmODAnMhnodmO<emo?km<FEnkmoUZnO_mNE]6Deco>[VWHmoNSYcb?Mo7b"
    A$ = A$ + "O>moeUgmi4Vo?[6l^goCjlOfhXbcnW]?H:eok=f^Zl_gI^kEKZHM?AoO_jjo"
    A$ = A$ + "E^6dYko[JmoAeRcFoCgoQleoGRaPioeUoa<fg[GInC_ocD?okNePSon_SmoM"
    A$ = A$ + "F?7E?gNdJNZjoSjmkZko[b?okmb]jm0?K?IU?3jEN>DZlC?oCVORnOFo_WJ^"
    A$ = A$ + "GEnMHnoXW1MUiOFOOEVooUahEgoIZl?j\W3m^GokNnE\VoXg1EbiO5mo]Z1g"
    A$ = A$ + "k>`[Lng]mEWEWgG3B_7Xbi^l[YoW7m_k:nla?kloEVOjioJ^GMn_jW]8ik7I"
    A$ = A$ + "Ri__OHJco^UOENUgobkLmoAOng9\n__?X;ePZf3nXnOTokmnoSgmUN\D]dW["
    A$ = A$ + "kdWoUoaD?;0CGgNWTE?_C=gkBnYG7B=gWhckB>_ek70mcD_G>M?#Yb?ml>]Y"
    A$ = A$ + ">gC_G?ml70hVdm1Yo?ZNM?mH00X3Bo<aZcG_O00<MYk5:AoOY[i000000000"
    A$ = A$ + "000000000000000000000`4Tj_OhGogUXYVObJ?00iO>LjN#VJ?HM9oD[md3"
    A$ = A$ + "Cml?iOf3TN?H7^o;M?0C<oSI?mlC\fCNYg3T>oeH8mj`DWkYcWlB_78mM?Om"
    A$ = A$ + "eLoN#Gcmdmo<al?>6BT=I]FkWlm?[a#jeRY>gC_fCFY?ogQWoWZ7Xmn?BD3>"
    A$ = A$ + ">gWfl?MnkSQZcVlBNoc:oY_o?MfM8Ogo<CYGok`M?joKVmOU>omaAR\9_d_o"
    A$ = A$ + "_^doIZjOj\k#nY77CFjeo^LoCR\Cgog:kDePTmoealC?6Z>kDBo<WdiOLLLK"
    A$ = A$ + "m?Mn9ko;icOFSQTIW<o9NgCgTNoWnodoG7jmY3iWN<DMfkZ\>T^7\^TOG^o="
    A$ = A$ + "eH8MoWCM^O<o4ICnmOMjnWDeNmoeSNOj#nY73EWmBZJ#GVg9c?mjoTc?mcOk"
    A$ = A$ + "B]?AfTOoWnodoGGjmY3iWN<DMfg\?oCdoG7Vk9bohjOZiOjmOjo;On9bNEoC"
    A$ = A$ + "UM7<in_BW_nojGoGYc?mHXj\CfkGjiN7bOMf?AfMX6<ikog[m9c_jLgG3BUo"
    A$ = A$ + "J<T<kYfoEjlCOnGoOm;odSQZc^CmoU^fWhlOW^oEoOcJogal[>gmePDi_639"
    A$ = A$ + "c^;WoWFnY?o[o_nUOja#eIWLNgaeo4eod_3bdkoVjmoken9goGbn?BWoDko:"
    A$ = A$ + "MnY?o[o?i_?6BUMRiM7ZoYc?mJ#jnOfG3ZLNOfmoYOogYb?ajnnj`Dc_;WoW"
    A$ = A$ + "Fnjo;ocoVNnkSQDIWHNgQjOjlGWmCEog[1C9Omom[iWHMOOM8EnC]oWYW_no"
    A$ = A$ + "bol_YWonH8Ef9VgMXnWnkn\Cgo>Ymojo;G]oN[3Yb_;WoTOmIgQnoFBol_YW"
    A$ = A$ + "OeIgQa#jiodGoWJoGW=oCTMjj_mojoCnc=oSmoU:oYn\W>Toaa`4WoMiiOYF"
    A$ = A$ + "kC?6BOogdcOjWodoWlCOn;MoGb?OnCNoGGNnCblCTMGFoWLn[a#jeoTiWnco"
    A$ = A$ + "200000000000000000000000000000000000000000000000000000000000"
    A$ = A$ + "00000000000000000000000000ekk_olod_?0lWcWohffOEUioeGo^DckTe1"
    A$ = A$ + "HJdo70IU^WWZkmkNcMmm1eAoO0TEjNOBfoG7VoDojmObook_]lGokn\mlENF"
    A$ = A$ + "#oO0TEjnOVNoO[LWBngkM^FekRMjiOfhh\oi[f:]Wmk10GSdmodQno^UMbn_"
    A$ = A$ + "Z\n_WeIiOemol:cogelC_ngYl?f_WNo0NWmZgeVhM0<YolO\G3BeoGbjoIIW"
    A$ = A$ + "H?`TkoAnolkk=F_OOne?=\lCOfOO<<]ioDcoA?k9EO?C:O0hSAgoG[b?mjOC"
    A$ = A$ + "bgWoFn0#1BoloeHHZiW^ng\lSd7`Tgo9O0VYd?oO=6VJnY[o=;Omo9O0X0YO"
    A$ = A$ + "noJ<<elCGoKFnjoCn0#1BoloeHHZiW^ng\leoWl1P2Tnio[a`Dc?Mm_Ai7k?"
    A$ = A$ + "30CNoWl10XHncoI`nNU?004AbnOBgoMjl10P9:I_OWmo50000000P?nnoaJo"
    A$ = A$ + "%%h1"
    btemp$ = ""
    For i& = 1 To Len(A$) Step 4: B$ = Mid$(A$, i&, 4)
        If InStr(1, B$, "%") Then
            For C% = 1 To Len(B$): F$ = Mid$(B$, C%, 1)
                If F$ <> "%" Then C$ = C$ + F$
            Next: B$ = C$: End If: For j = 1 To Len(B$)
            If Mid$(B$, j, 1) = "#" Then
        Mid$(B$, j) = "@": End If: Next
        For t% = Len(B$) To 1 Step -1
            B& = B& * 64 + Asc(Mid$(B$, t%)) - 48
            Next: X$ = "": For t% = 1 To Len(B$) - 1
            X$ = X$ + Chr$(B& And 255): B& = B& \ 256
    Next: btemp$ = btemp$ + X$: Next
    btemp$ = _Inflate$(btemp$)
    _MemPut m, m.OFFSET, btemp$: _MemFree m
    BASIMAGE1& = _CopyImage(v&): _FreeImage v&
End Function

Print this item

  Tough to ID key sequences...
Posted by: Pete - 12-03-2022, 08:38 PM - Forum: General Discussion - Replies (4)

One ID key sequence toggle tough to identify is Ctrl + Ctrl -. It is often used in browser to enlarge or decrease text size.

I love INKEY$ but it is worthless for this one. When Ctrl is held using a PEEK routine to identify it, the + and - keys simply do not get picked up by INKEY$. Now with Windows API we can accomplish this with...

Code: (Select All)
CONST VK_CONTROL = &H11
CONST VK_OEM_PLUS = &HBB

CONST VK_OEM_MINUS = &HBD
CONST VK_ESCAPE = &H1B

DECLARE DYNAMIC LIBRARY "User32"
    FUNCTION GetAsyncKeyState% (BYVAL vkey AS LONG)
END DECLARE

DO
    _LIMIT 30
    IF GetAsyncKeyState%(VK_CONTROL) AND GetAsyncKeyState%(VK_OEM_PLUS) THEN
        PRINT "Ctrl +"
    END IF
    IF GetAsyncKeyState%(VK_CONTROL) AND GetAsyncKeyState%(VK_OEM_MINUS) THEN
        PRINT "Ctrl -"
    END IF
LOOP UNTIL GetAsyncKeyState%(VK_ESCAPE)

But what about x-platform methods?

Well, here is my shout out to _KEYHIT using Ctrl+ and Ctrl- to change font sizes...

Code: (Select All)
SCREEN 0
fontsize% = 16
style$ = "monospace"
fontpath$ = ENVIRON$("SYSTEMROOT") + "\fonts\lucon.ttf"
DIM font(8 TO 32) AS LONG
FOR i = 8 TO 32 STEP 2
    font(i) = _LOADFONT(fontpath$, i, style$)
NEXT
_FONT font(fontsize%)
ww = 600: wh = 350
WIDTH ww \ _FONTWIDTH, wh \ _FONTHEIGHT
PALETTE 7, 63: COLOR 0, 7: CLS
_FONT font(fontsize%)
_SCREENMOVE 0, 0

PRINT "Press ctrl + to increase font size or ctrl - to decrease."
DO
    _LIMIT 30
    c = _KEYHIT
    IF c THEN
        SELECT CASE c
            CASE -189
                IF fontsize% > 9 THEN fontsize% = fontsize% - 2: resizeit fontsize%, ww, wh, font()
            CASE -187
                IF fontsize% < 31 THEN fontsize% = fontsize% + 2: resizeit fontsize%, ww, wh, font()
        END SELECT
    END IF
LOOP

SUB resizeit (fontsize%, ww, wh, font() AS LONG)
    _FONT font(fontsize%)
    fw% = 0: fh% = 0
    DO
        fw% = _FONTWIDTH: fh% = _FONTHEIGHT
        IF fw% <> 0 AND fh% <> 0 THEN EXIT DO
        _DELAY .1
    LOOP
    WIDTH ww / fw%, wh / fh%
    PALETTE 7, 63: COLOR 0, 7: CLS
    _FONT font(fontsize%)
    DO: LOOP UNTIL _SCREENEXISTS: _SCREENMOVE 0, 0
    PRINT "Font size changed to:"; fontsize%: PRINT: PRINT "Window width:"; _WIDTH
    _KEYCLEAR
END SUB

Pete

Print this item

  Day 023: INKEY$
Posted by: Pete - 12-03-2022, 07:18 PM - Forum: Keyword of the Day! - Replies (1)

INKEY$ is one of several methods to communicate with the keyboard. Others are _KEYHIT, _KEYDOWN, INP(), PEEK and POKE, and for Windows users, the function GetAsyncKeyState%.

So why is INKEY$ so much better than those other choices, why because I code with it, of course!

Well, my comedy bit aside, INKEY$ is comfortable for me because it has easily recognized key associations, by string representation of ASCII values.

Terminate loop with the Esc key example...

Code: (Select All)
DO
_LIMIT 30
LOOP UNTIL INKEY$ = CHR$(27) ' The string character for ASCII code 27.

Since INKEY$ functions as a string, unlike the other alternatives, we need recognize a string is true or false by being either filled or null.

Terminate loop with press almost any key...
Code: (Select All)
DO
_LIMIT 30
LOOP UNTIL LEN(INKEY$) ' Loop exits when INKEY$ is no longer null, (e.g. INKEY$ = "").

I find the best way to set up a keyboard poll routine is by assigning a variable to INKEY$...

Code: (Select All)
DO
    _LIMIT 30
    mykey$ = INKEY$
    IF LEN(mykey$) THEN
        SELECT CASE mykey$
            CASE CHR$(27)
                PRINT " INKEY$ CODE: CHR$(27)"
                EXIT DO ' Escape loop to end program snippet.
            CASE ELSE
                show_Values (mykey$)
        END SELECT
    END IF
LOOP

SUB show_Values (mykey$)
    SELECT CASE LEN(mykey$)
        CASE 1
            b = ASC(mykey$) ' ASC() converts a string character to a numeric value.
            a$ = ""
        CASE 2
            b = ASC(MID$(mykey$, 2, 1))
            a$ = "CHR$(0)" ' This is the nul character INKEY$ reports for 2 byte length key representation like the F1 - F12 keys.
    END SELECT
    b$ = LTRIM$(STR$(b)) ' This is how you convert a numeric variable to a string variable.
        PRINT " INKEY$ CODE: ";
        IF LEN(a$) THEN PRINT a$; " + ";
        PRINT "CHR$("; b$; ") "; "AKA: " + CHR$(34) + CHR$(b) + CHR$(34) + " ";
END SUB

"...press almost any key." WTH is that???

Well, INKEY$ does not register for certain keys like...

Shift
Ctrl
Alt
Fn
Windows key
PrtScr
NumLock
Capslock

These keys need to be referenced in some other either supportive method or via a different key recognition keyword like _KEYDOWN.

An "old school" method I love, and successfully lobbied for 15+ years ago, is the supportive method of using PEEK addresses.

This routine identifies if Alt, Ctrl, or Shift keys are held down while other keys are pressed...
Code: (Select All)
_CONTROLCHR OFF '  Allows us to key input combinations that would otherwise adversely affect screen output.
DO
    _LIMIT 30
    DEF SEG = 0
    IF PEEK(1047) MOD 16 = 1 OR PEEK(1047) MOD 16 = 2 THEN
        KeyCombos = 1 ' Shift
    ELSEIF PEEK(1047) MOD 16 = 3 OR PEEK(1047) MOD 16 = 4 THEN
        KeyCombos = 2 ' Ctrl
    ELSEIF PEEK(1047) MOD 16 = 5 OR PEEK(1047) MOD 16 = 6 THEN
        KeyCombos = 3 ' Ctrl+Shift
    ELSEIF PEEK(1047) MOD 16 = 7 OR PEEK(1047) MOD 16 = 8 THEN
        KeyCombos = 4 ' Alt
    ELSEIF PEEK(1047) MOD 16 = 9 OR PEEK(1047) MOD 16 = 10 THEN
        KeyCombos = 5 ' Shift+Alt
    ELSEIF PEEK(1047) MOD 16 = 12 THEN
        KeyCombos = 6 ' Ctrl+Alt
    ELSEIF PEEK(1047) MOD 16 = 14 THEN
        KeyCombos = 7 ' Shift+Ctrl+Alt
    ELSE
        KeyCombos = 0
    END IF
    DEF SEG

    mykey$ = INKEY$
    IF LEN(mykey$) OR KeyCombos THEN
        SELECT CASE mykey$
            CASE CHR$(27)
                PRINT " INKEY$ CODE: CHR$(27)"
                EXIT DO ' Escape loop to end program snippet.
        END SELECT
        show_Values mykey$, KeyCombos
    END IF
LOOP

SUB show_Values (mykey$, KeyCombos)
    STATIC OldKeyCombos
    IF KeyCombos = OldKeyCombos AND LEN(mykey$) = 0 THEN EXIT SUB ' Neat trick to only print once to screen while PEEK discovered keys are held down.
    DO ' Falx loop to avoid printing INKEY printing if only a non-INKEY$ is held down.
        SELECT CASE LEN(mykey$)
            CASE 0
                EXIT DO
            CASE 1
                b = ASC(mykey$) ' ASC() converts a string character to a numeric value.
                a$ = ""
            CASE 2
                b = ASC(MID$(mykey$, 2, 1))
                a$ = "CHR$(0)" ' This is the nul character INKEY$ reports for 2 byte length key representation like the F1 - F12 keys.
        END SELECT
        b$ = LTRIM$(STR$(b)) ' This is how you convert a numeric variable to a string variable.
        PRINT " INKEY$ CODE: ";
        IF LEN(a$) THEN PRINT a$; " + ";
        PRINT "CHR$("; b$; ") "; "AKA: " + CHR$(34) + CHR$(b) + CHR$(34) + " ";
        EXIT DO
    LOOP
    IF KeyCombos THEN
        SELECT CASE KeyCombos
            CASE 1
                PRINT " Shift Down";
            CASE 2
                PRINT " Crtl Down";
            CASE 3
                PRINT " Shift + Ctrl Down";
            CASE 4
                PRINT " Alt Down";
            CASE 5
                PRINT " Shift + Alt Down";
            CASE 6
                PRINT " Ctrl + Alt Down";
            CASE 7
                PRINT " Shift + Ctrl + Alt Down";
        END SELECT
        OldKeyCombos = KeyCombos
    END IF
    PRINT
END SUB

Cool, right? Well, not so fast there Sparky, there is a drawback. We can only read 3 key inputs at a time. Hold Shift + Ctrl + Alt then press "A" and just like I stated, the 4th input, "A" won't get printed. INP() won't help here either, and interestingly enough, although _KEYHIT and _KEYDOWN can handle most 3 key combinations, I noted some arrow combos and others were not registered when trying to get that technique to work. WinAPI can recognize some but not all 4+ multiple key presses. Fool around with some keys in this example if you have a Windows System. Hold Shift+Ctrl+Alt and try some arrow keys etc to see which ones show up for INKEY$. Some will, some won't, but those are all 4 key press events with this INKEY$ helper.

The only key I set to register in the alphabet is "A" so try Shift+Ctrl+Alt+A to see them all register. Oh, if you fooled with the code to try and get A+B+C+D to register, it would only register A+B+C.

Code: (Select All)
CONST VK_SHIFT = &H10 'SHIFT key
CONST VK_CONTROL = &H11 'CTRL key
CONST VK_MENU = &H12 'ALT key

DECLARE DYNAMIC LIBRARY "User32"
    FUNCTION GetAsyncKeyState% (BYVAL vkey AS LONG)
END DECLARE

DO
    _LIMIT 30
    a% = GetAsyncKeyState%(VK_SHIFT)
    b% = GetAsyncKeyState%(VK_CONTROL)
    c% = GetAsyncKeyState%(VK_MENU)
    d% = GetAsyncKeyState%(&H41)
    SELECT CASE a%
        CASE 0
        CASE ELSE
            PRINT a%
    END SELECT
    PRINT a%, b%, c%, d%, INKEY$
LOOP

So what else can we say about INKEY$? Well, it can be used in combination with other keyboard keywords. Maybe not good practice, but just saying it's workable.

INKEY$ stores key presses in a buffer. You can use _KEYCLEAR to clear that buffer. Some programs need this to stop users who press keys during a program pause from being used in the routine. SLEEP is actually a very simple example...

Code: (Select All)
SLEEP
IF LEN(INKEY$) THEN BEEP ' Will Beep
SLEEP
_KEYCLEAR
IF LEN(INKEY$) THEN BEEP ' Won't beep.

A bit more involved example of the buffer uses a delay to slow the typing output to the screen. With the buffer, Demo 1, the output catches up after you are done fast typing. In demo 2, _KEYCLEAR clears the buffer and the utput stops the moment the user stops fast typing.

Code: (Select All)
PRINT "Demo 1: Buffer will catch up to your typing. Press Es when ready for demo 2..": PRINT
LOCATE , , 1 ' Let's show the cursor for these typing demos...
' That third LOCATE parameter of 1 gives us the underline stype cursor.
' See the LOCATE Keyword of the Day for other cursor appearance options).
DO
    _LIMIT 30
    mykey$ = INKEY$
    IF LEN(mykey$) OR KeyCombos THEN
        SELECT CASE mykey$
            CASE CHR$(27)
                EXIT DO
            CASE ELSE
                IF LEN(mykey$) = 1 THEN
                    PRINT mykey$;
                    _DELAY .25
                END IF
        END SELECT
    END IF
LOOP
PRINT: PRINT: PRINT "Okay, demo two. The printing to the screen stops the moment you stop typing.": PRINT
DO
    _LIMIT 30
    mykey$ = INKEY$
    IF LEN(mykey$) OR KeyCombos THEN
        SELECT CASE mykey$
            CASE CHR$(27)
                EXIT DO
            CASE ELSE
                IF LEN(mykey$) = 1 THEN
                    PRINT mykey$;
                    _KEYCLEAR
                    _DELAY .25
                END IF
        END SELECT
    END IF
LOOP

On a final note, my beloved INKEY$, as stated previously, can't do everything. For instance say you want to use Ctrl + and Ctrl - to resize text, just like browsers. Well, we can't do that with INKEY$, but we can use _KEYHIT or Win32 API. An example of each is provided here: https://qb64phoenix.com/forum/showthread.php?tid=1230

Pete

Print this item

  DAY 023: MOD
Posted by: Pete - 12-03-2022, 06:08 PM - Forum: General Discussion - No Replies

OH HELL NO!!!!!!!!! Big Grin Big Grin Big Grin

Just a spoof. This is in General Discussion, not our KEYWORD board.

The real Keyword of the Day is here: https://qb64phoenix.com/forum/showthread.php?tid=1229

Pete

Print this item

  Remainder(n, d) Better than MOD, same as capping wrapping?
Posted by: bplus - 12-03-2022, 05:22 PM - Forum: Utilities - Replies (13)

Break this? 
For ideal modulus, I imagine it should be between 0 and the divisor = modulus whether divisor is pos or negative.

I read johannhowitzer Wrapping, capping and other... and it all fell in place what we are trying to reach, keeping numbers between 0 and the divisor whether integer or float like Pi, we just want a proper remainder.

Code: (Select All)
_Title "Remainder test" ' b+ for a modulus that always returns a number between 0 and divisor
' if divisor is negative then return a rational between 0 and some rational d < 0
' if divisor is positive then return a rational between 0 and some rational d > 0
' if divisor is 0 ? can't divide by 0
' Do we need to round? Doesn't look like it but I just ran a couple quick tests. Folks here can find fault with anything! ;-))

' NOTE: when testing don't leave space between , and d  eg, do 5,3 not 5, 3

$Console:Only
Do
    Input "0's quit, please enter n, d to find remainder n/d between 0 and d "; n, d
    If (n = 0) Or (d = 0) Then End
    Print Remainder##(n, d)
    Print
Loop

' modeled on MODn
'ref Pete  https://qb64phoenix.com/forum/showthread.php?tid=1195&pid=10983#pid10983
Function Remainder## (n##, d##)
    If d## = 0 Then Exit Function
    Remainder## = n## - (d##) * Int(n## / (d##))
End Function

BTW best toggle I've seen and used often from Chia Pet:
toggle = 1 - toggle

Print this item

  Wrapping, capping, toggling, and slicing, oh my!
Posted by: johannhowitzer - 12-03-2022, 11:24 AM - Forum: One Hit Wonders - Replies (4)

Who knows, maybe everyone out there is already using these things in some form,
but I use them EVERYWHERE and maybe someone will enjoy them!  They're just simple
and common ways to manipulate values, but they will replace multiple lines of code
and make said code much easier to understand at a glance.



Code: (Select All)
function wrap(n, l1, h1) ' n is adjusted back within lower(l) and upper(h) bounds similar to mod operator
l = l1: h = h1 ' make sure h is never less than l, this also prevents division by zero
if h1 < l1 then
  l = h1: h = l1
end if
x = (l - n) / ((h - l) + 1)
if x <> int(x) then x = x + 1
wrap = n + (int(x) * ((h - l) + 1))
end function

This first function wraps a value between two values.  You pass n, and if it's beyond one
of the bounds, it wraps back around.  So say you have five menu options, and the user moves
the cursor to position 6, this will wrap it back around to 1.  Going to 0 will also wrap around
to 5.  It doesn't matter how far outside you go, and negatives are treated properly, so -256
in this example will get wrapped correctly to 4.

The bounds can be in any order, and n is usually going to be a simple addition or subtraction.
So in the case of the above menu example, [wrap(cursor + 1, 1, 5)] will move the cursor, with
the check to wrap around.  And in most menu navigation scenarios, wrapping is super nice for
the user.  So you can write the following:

if [input] = [up arrow]  then cursor = wrap(cursor - 1, 1, [max])
if [input] = [down arrow] then cursor = wrap(cursor + 1, 1, [max])

You could also use this to wrap coordinates, so the player can leave the right side of your
stage and emerge on the left, like the Pac-Man tunnel, or create a seemingly infinitely scrolling
stage, like Asteroids, which really just repeats on itself.  For the latter example, you would
wrap the coordinates of objects whenever they travel beyond the stage, and also wrap the
coordinates to within a range around the player's position, when using them to draw the screen.
The result will appear seamless, and it won't matter how fast things travel each frame.



Code: (Select All)
function wrap_a(a) ' angle a is adjusted back within 0 and 2pi, noninclusive of 2pi
x = -a / atn1(8)
if x <> int(x) then x = x + 1
wrap_a = a + (int(x) * atn1(8))
end function


function atn1(n)
atn1 = n * atn(1)
end function

This does exactly the same thing, but for angles.  Put any angle value in this function,
and it will simplify it to a positive angle less than a full circle.  Very useful when
angle values can get adjusted over and over.  Just put the function around any angle changes.

(Note that the little atn1() function is just a shorthand thing, I got tired of typing
n * atn(1) in a ton of places.  It generates an angle in radians, where the value passed in
is an eighth of a circle or 45 degrees, so atn1(8) = 2 * pi radians, or 360 degrees.)



Code: (Select All)
function plus_limit(n, p, l) ' p is added to n, but can't go past l in the direction of travel
q = n + p
if sgn(q - l) = sgn(p) then q = l
plus_limit = q
end function

This saves a lot of space capping values, and is direction-dependent.  If p is positive,
n can't go past l upward; if p is negative, n can't go past l downward.  I use this for many
things, like decaying values without going below zero, capping healing at maximum health,
trapping position coordinates at the edges of the screen, the uses are endless.  The syntax
is very clean, all packed up in one line, it replaces stuff like this:

health = health + 100
if health > max_health then health = max_health

with this:

health = plus_limit(health, 100, max_health)

The directional dependency of this is more useful than you might think at first glance,
and is a feature that simple floor and ceiling functions don't have.  For example,
you can use sgn() comparison to move something toward a goal value, and stop it if it
reaches the goal, but not snap to the goal if moving away from it.
Then you might use something like this:

x = plus_limit(x, sgn(goal - x) * speed, goal)

This can be very useful in having one value that changes instantly, and another that
constantly follows it.  In my current game project, one way I use this is to give visual
feedback about damage that was just taken.  If the player gets hit, the health is instantly
lowered, so the green health bar gets shorter.  But there's a hidden second red bar behind
it, and that red bar's value follows the health value.  If you gain health, this does
nothing, since the red bar will be shorter, but when you LOSE health, the player will see
part of the health bar become red, and instantly start shrinking, until all that's left
is the green part.

Do note that because of the dependency, if for example you are subtracting, this function
won't catch a value that was already higher than it should be.  So be careful.



Code: (Select All)
function toggle(v, p, q)
toggle = v
if v = p then toggle = q
if v = q then toggle = p
end function

This one just toggles between the given values.  However, it will not do anything
if the variable passed is not one of these two values.  Use like v = toggle(v, value1, value2).
Replaces the messier [if v = value1 then v = value2 else v = value1].  And since it's a function,
you can use it very compactly and dynamically, rather than directly manipulating variables.
Let's say you have a 1v1 RPG combat scenario, and one of the characters decided to attack.
You've calculated the damage already.  So instead of:

if turn = char1 then target = char2 else target = char1
call damage_char(target, damage)

You can write:

call damage_char(toggle(turn, char1, char2), damage)

Think of it like the binary NOT operator, except this works between any two values you want.



Code: (Select All)
function before$(t$, c$)
p = instr(t$, c$)
if p = false then p = len(t$) + 1
before$ = left$(t$, p - 1)
end function


function after$(t$, c$)
after$ = right$(t$, len(t$) - instr(t$, c$) - (len(c$) - 1))
end function


function between$(t$, c1$, c2$)
between$ = before$(after$(t$, c1$), c2$)
end function

These functions are used for slicing strings, which has been useful in developing my own scripting,
among other things.  before$ will return everything in t$ before the first instance it finds of c$,
while after$ will return everything after it.  If c$ is not found, it will simply return t$ unchanged.

An interesting application of this is to return more than one value from a function.  For example,
if you want a function to return both x and y coordinates, you can make the function's return data type
a string, pack the two values up with a comma between like "100, 50", then use before and after
functions with val() to pull the values apart outside the function.  You can also easily do this
for three-dimensional coordinates.  And it need not be limited to coordinates, you could also use
a separator character to return multiple strings packed into one string, like "Alice|Timothy|Eric".

between$, of course, just cleans up the syntax when using before$ and after$ together, which often
happens in my code.  This:

result$ = before$(after$(t$, "["), "]")

Becomes this:

result$ = between$(t$, "[", "]")

Print this item

  Data setting tutorial video!
Posted by: johannhowitzer - 12-03-2022, 11:18 AM - Forum: Learning Resources and Archives - Replies (6)

Is your code filled with blocks of variable setting or DATA statements?  Is code you wrote years ago now indecipherable to you as a result?  Suffer no longer!  Table routines and abstraction are here to save the day.

This tutorial video isn't really QB64 specific, but hopefully people will get some mileage out of it, and find ways to make their code clearer and less bulky.

Print this item

  And now, Deep Thoughts, by Jack Handy...
Posted by: Pete - 12-03-2022, 06:24 AM - Forum: General Discussion - Replies (2)

If you overly nest a looping routine, could you find yourself in deep DO: DO?

Print this item