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

Username/Email:
  

Password
  





Search Forums

(Advanced Search)

Forum Statistics
» Members: 483
» Latest member: aplus
» Forum threads: 2,804
» Forum posts: 26,431

Full Statistics

Latest Threads
Fast QB64 base64 encoder ...
Forum: a740g
Last Post: a740g
1 hour ago
» Replies: 3
» Views: 420
Mean user base makes Stev...
Forum: General Discussion
Last Post: bobalooie
2 hours ago
» Replies: 7
» Views: 172
What do you guys like to ...
Forum: General Discussion
Last Post: bplus
2 hours ago
» Replies: 1
» Views: 26
_IIF limits two question...
Forum: General Discussion
Last Post: bplus
3 hours ago
» Replies: 6
» Views: 98
DeflatePro
Forum: a740g
Last Post: a740g
4 hours ago
» Replies: 2
» Views: 53
New QBJS Samples Site
Forum: QBJS, BAM, and Other BASICs
Last Post: dbox
Yesterday, 06:16 PM
» Replies: 25
» Views: 890
Raspberry OS
Forum: Help Me!
Last Post: Jack
Yesterday, 05:42 PM
» Replies: 7
» Views: 151
InForm-PE
Forum: a740g
Last Post: Kernelpanic
Yesterday, 05:22 PM
» Replies: 80
» Views: 6,147
GNU C++ Compiler error
Forum: Help Me!
Last Post: RhoSigma
Yesterday, 11:57 AM
» Replies: 1
» Views: 62
Merry Christmas Globes!
Forum: Programs
Last Post: SierraKen
Yesterday, 03:46 AM
» Replies: 10
» Views: 135

 
  Stonehenge Board Game
Posted by: Donald Foster - 05-28-2024, 03:35 PM - Forum: Donald Foster - Replies (3)

Hello Everyone,

Here is my take on the 2 player abstract strategy board game Stonehenge where players a racing to be the first player to claim 8 separate lines.

Hope you enjoy playing

Donald

https://youtu.be/4_hu9PpVbfU

[Image: Stone-Henge-Screenshot.png]


[Image: Stonehemge-play.webp]


[Image: Stonehemge-play4.webp]


.pdf   Stone Henge Description.pdf (Size: 49.57 KB / Downloads: 81)
.pdf   Stonehenge_English_Rules.pdf (Size: 53.45 KB / Downloads: 95)

Code: (Select All)
_TITLE "Stone Henge Board Game"

SCREEN _NEWIMAGE(1325, 740, 256)

_PALETTECOLOR 1, _RGB32(72, 72, 72) ' Background Color
_PALETTECOLOR 2, _RGB32(50, 50, 50) ' Board Color
_PALETTECOLOR 3, _RGB32(170, 170, 170) ' Druid Space Color
_PALETTECOLOR 4, _RGB32(0, 255, 0) ' MegalLith Space Color
_PALETTECOLOR 5, _RGB32(255, 255, 0) ' MegalLith Line Color
_PALETTECOLOR 6, _RGB32(254, 254, 0) ' MegalLith Line Color
_PALETTECOLOR 7, _RGB32(155, 0, 0) ' Player 2 Druid Piece Color

DIM AS _BYTE X, Y, Z, Player, Opponent, Row, Column
DIM AS INTEGER X1, X2
DIM SHARED AS _BYTE Selected, HidePieces, Piece, DruidPieces(2, 6), Megaliths(2), Columns(5), DruidSpace(7, 12), MegalithSpace(7, 12)
DIM SHARED AS _BYTE BoardPlayer(7, 12), BoardPiece(7, 12), LineTotal(2, 15), EligibleWinner(15), LineWinner(15), PieceColor(2)
DIM SHARED AS _BYTE LineSize(15), LineComplete(15), LineRow(15, 5), LineColumn(15, 5), DruidShown(7, 12)
DIM SHARED AS _BYTE LastRow, LastColumn, Megalith
DIM SHARED AS INTEGER BoardX(7, 12), BoardY(7, 12), DruidX(2, 6), DruidY(2, 6)

DruidPieces(1, 1) = 2: DruidPieces(1, 2) = 2: DruidPieces(1, 3) = 2: DruidPieces(1, 4) = 1: DruidPieces(1, 5) = 1: DruidPieces(1, 6) = 1
DruidPieces(2, 1) = 2: DruidPieces(2, 2) = 2: DruidPieces(2, 3) = 2: DruidPieces(2, 4) = 1: DruidPieces(2, 5) = 1: DruidPieces(2, 6) = 1
Megaliths(1) = 8: Megaliths(2) = 8
Columns(1) = 4: Columns(2) = 5: Columns(3) = 4: Columns(4) = 3: Columns(5) = 2
Player = 1: Opponent = 2: PieceColor(1) = 0: PieceColor(2) = 7

' Setup Line Sizes
DATA 2,3,4,5,4,2,3,4,5,4,2,3,4,5,4
FOR Z = 1 TO 15: READ LineSize(Z): NEXT

' Setup Line Rows and Column
DATA 2,4,3,3,2,6,3,5,4,4,2,8,3,7,4,6,5,5,2,10,3,9,4,8,5,7,6,6,3,11,4,10,5,9,6,8
DATA 2,10,3,11,2,8,3,9,4,10,2,6,3,7,4,8,5,9,2,4,3,5,4,6,5,7,6,8,3,3,4,4,5,5,6,6
DATA 6,6,6,8,5,5,5,7,5,9,4,4,4,6,4,8,4,10,3,3,3,5,3,7,3,9,3,11,2,4,2,6,2,8,2,10
FOR Z = 1 TO 15: FOR Y = 1 TO LineSize(Z): READ LineRow(Z, Y), LineColumn(Z, Y): NEXT: NEXT

' Clear Drois Piece Spaces and Megalith Spaces
FOR Z = 1 TO 7: FOR Y = 1 TO 12: DruidSpace(Z, Y) = 0: MegalithSpace(Z, Y) = 0: NEXT: NEXT

' Setup Druid Spaces
DATA 2,4,2,6,2,8,2,10,3,3,3,5,3,7,3,9,3,11,4,4,4,6,4,8,4,10,5,5,5,7,5,9,6,6,6,8
FOR Z = 1 TO 18: READ Row, Column: DruidSpace(Row, Column) = 1: NEXT

' Setup Megalith Spaces
DATA 1,5,1,7,1,9,1,11,2,12,4,12,5,11,6,10,7,9,7,7,6,4,5,3,4,2,3,1,2,2
FOR Z = 1 TO 15: READ Row, Column: MegalithSpace(Row, Column) = Z: NEXT

' Setup BoardX and BoardY
X1 = 70
FOR Z = 1 TO 7
   X2 = 70
   FOR Y = 1 TO 12
      BoardX(Z, Y) = X2: BoardY(Z, Y) = X1
      X2 = X2 + 58
   NEXT
   X1 = X1 + 100
NEXT

' Draw Board
CLS , 1: LINE (10, 10)-(770, 730), 15, BF: LINE (12, 12)-(768, 728), 2, BF

' Draw Megalith Lines
PSET (BoardX(1, 5) + 3, BoardY(1, 5)), 6: DRAW "TA60L200U5R200D5TA0BL2P6,6"
PSET (BoardX(1, 7) + 3, BoardY(1, 7)), 6: DRAW "TA60L350U5R350D5TA0BL2P6,6"
PSET (BoardX(1, 9) + 3, BoardY(1, 9)), 6: DRAW "TA60L450U5R450D5TA0BL2P6,6"
PSET (BoardX(1, 11) + 3, BoardY(1, 11)), 6: DRAW "TA60L550U5R550D5TA0BL2P6,6"
PSET (BoardX(2, 12) + 3, BoardY(2, 12)), 6: DRAW "TA60L450U5R450D5TA0BL2P6,6"
PSET (BoardX(4, 12) - 3, BoardY(4, 12)), 5: DRAW "TA120R200D5L200U5TA0BU2P5,5"
PSET (BoardX(5, 11) - 3, BoardY(5, 11)), 5: DRAW "TA120R350D5L350U5TA0BU2P5,5"
PSET (BoardX(6, 10) - 3, BoardY(6, 10)), 5: DRAW "TA120R450D5L450U5TA0BU2P5,5"
PSET (BoardX(7, 9) - 3, BoardY(7, 9)), 5: DRAW "TA120R550D5L550U5TA0BU2P5,5"
PSET (BoardX(7, 7) - 3, BoardY(7, 7)), 5: DRAW "TA120R450D5L450U5TA0BU2P5,5"
LINE (BoardX(2, 2), BoardY(2, 2) - 2)-(BoardX(2, 10), BoardY(2, 10) + 2), 5, BF
LINE (BoardX(3, 1), BoardY(3, 1) - 2)-(BoardX(3, 11), BoardY(3, 11) + 2), 5, BF
LINE (BoardX(4, 2), BoardY(4, 2) - 2)-(BoardX(4, 10), BoardY(4, 10) + 2), 5, BF
LINE (BoardX(5, 3), BoardY(5, 3) - 2)-(BoardX(5, 9), BoardY(5, 9) + 2), 5, BF
LINE (BoardX(6, 4), BoardY(6, 4) - 2)-(BoardX(6, 8), BoardY(6, 8) + 2), 5, BF

' Draw Druid Spaces and Megalith Spaces
FOR Z = 1 TO 7
   FOR Y = 1 TO 12
      IF DruidSpace(Z, Y) = 1 THEN CIRCLE (BoardX(Z, Y), BoardY(Z, Y)), 35, 3: PAINT (BoardX(Z, Y), BoardY(Z, Y)), 3
      IF MegalithSpace(Z, Y) > 0 THEN CIRCLE (BoardX(Z, Y), BoardY(Z, Y)), 25, 4: PAINT (BoardX(Z, Y), BoardY(Z, Y)), 4
   NEXT
NEXT

fontpath$ = ENVIRON$("SYSTEMROOT") + "\fonts\Segoeuib.ttf"

' Display Game Title
COLOR 4, 1: font& = _LOADFONT(fontpath$, 45): _FONT font&: _PRINTSTRING (835, 10), "S T O N E   H E N G E"

' Hide Druid Piece Values?
COLOR 15, 1: font& = _LOADFONT(fontpath$, 25): _FONT font&: _PRINTSTRING (848, 700), "Hide Druid Piece Values? ( Y or N )"

HidePiecesInput: A$ = UCASE$(INKEY$): IF A$ = "" GOTO HidePiecesInput
IF ASC(A$) = 27 AND FullScreen = 0 THEN FullScreen = -1: _FULLSCREEN _SQUAREPIXELS , _SMOOTH ELSE IF ASC(A$) = 27 THEN FullScreen = 0: _FULLSCREEN _OFF
IF A$ = "Y" THEN HidePieces = 1 ELSE IF A$ = "N" THEN HidePieces = 0 ELSE GOTO HidePiecesInput

COLOR 5, 1: font& = _LOADFONT(fontpath$, 30): _FONT font&: _PRINTSTRING (848, 700), STRING$(250, 32)

IF HidePieces = 1 THEN COLOR 3, 1: font& = _LOADFONT(fontpath$, 30): _FONT font&: _PRINTSTRING (871, 60), "Druid Piece Values Hiden"

COLOR 15, 1: font& = _LOADFONT(fontpath$, 30): _FONT font&: _PRINTSTRING (861, 210), "D  R  U  I  D    P  I  E  C  E  S"

' Display Player Pieces
X1 = 310
FOR Z = 1 TO 3
   DrawDruidPiece 827, X1, 1, Z, 1: DrawDruidPiece 962, X1, 1, 3 + Z, 1: DrawDruidPiece 1097, X1, 2, Z, 1: DrawDruidPiece 1232, X1, 2, 3 + Z, 1
   DruidX(1, Z) = 827: DruidY(1, Z) = X1: DruidX(1, 3 + Z) = 962: DruidY(1, 3 + Z) = X1
   DruidX(2, Z) = 1097: DruidY(2, Z) = X1: DruidX(2, 3 + Z) = 1232: DruidY(2, 3 + Z) = X1
   X1 = X1 + 100
NEXT

COLOR 3, 1: font& = _LOADFONT(fontpath$, 35): _FONT font&
FOR Z = 1 TO 2: FOR Y = 1 TO 6: _PRINTSTRING (DruidX(Z, Y) + 47, DruidY(Z, Y) - 35), " X": NEXT: NEXT

DrawMegaLith 830, 620, 1: COLOR 3, 1: _PRINTSTRING (870, 605), "X"
DrawMegaLith 1197, 620, 2: COLOR 3, 1: _PRINTSTRING (1237, 605), "X"

GameLoop:
' Display Player's Indicator
DrawMegaLith 1047, 130, Player
COLOR 15, 1: font& = _LOADFONT(fontpath$, 20): _FONT font&: _PRINTSTRING (1005, 165), "Player: " + STR$(Player)

UpdateDruidList: EndTurnButton 0

COLOR 15, 1: font& = _LOADFONT(fontpath$, 25): _FONT font&: _PRINTSTRING (890, 705), "Choose Druid Piece to Play"

COLOR 5, 1: font& = _LOADFONT(fontpath$, 35): _FONT font&

PieceInput:
DO WHILE _MOUSEINPUT
   FOR Z = 1 TO 6
      IF _MOUSEX > DruidX(Player, Z) - 35 AND _MOUSEX < DruidX(Player, Z) + 35 AND _MOUSEY > DruidY(Player, Z) - 35 AND _MOUSEY < DruidY(Player, Z) + 35 THEN Selected = 1 ELSE Selected = 0

      IF HidePieces = 1 AND Selected = 1 THEN
         _PRINTSTRING (DruidX(Player, Z) + 50, DruidY(Player, Z)), STR$(DruidPieces(Player, Z))
      ELSEIF HidePieces = 1 AND Selected = 0 THEN
         _PRINTSTRING (DruidX(Player, Z) + 52, DruidY(Player, Z)), " ?"
      END IF

      IF _MOUSEBUTTON(1) = -1 AND DruidPieces(Player, Z) > 0 AND Selected = 1 THEN GOSUB ReleaseButton: Piece = Z: GOTO ChooseBoardSpace

   NEXT
LOOP
A$ = INKEY$: IF A$ <> "" THEN IF ASC(A$) = 27 AND Full = 0 THEN _FULLSCREEN _SQUAREPIXELS , _SMOOTH: Full = 1 ELSE IF ASC(A$) = 27 THEN _FULLSCREEN _OFF: Full = 0
GOTO PieceInput

ChooseBoardSpace:
IF HidePieces = 0 THEN
   LINE (DruidX(Player, Piece) - 45, DruidY(Player, Piece) - 45)-(DruidX(Player, Piece) + 45, DruidY(Player, Piece) + 45), 15, B
ELSE
   _PRINTSTRING (DruidX(Player, Piece) + 52, DruidY(Player, Piece)), " ?"
END IF

COLOR 15, 1: font& = _LOADFONT(fontpath$, 25): _FONT font&: _PRINTSTRING (890, 705), STRING$(300, 32)
_PRINTSTRING (825, 672), "Choose a Different Druid Piece to Play"
_PRINTSTRING (807, 705), "Choose Board Space to Place Druid Piece"

COLOR 5, 1: font& = _LOADFONT(fontpath$, 35): _FONT font&

BoardSpaceInput:
DO WHILE _MOUSEINPUT

   ' Choose Different Piece
   FOR Z = 1 TO 6
      IF _MOUSEX > DruidX(Player, Z) - 35 AND _MOUSEX < DruidX(Player, Z) + 35 AND _MOUSEY > DruidY(Player, Z) - 35 AND _MOUSEY < DruidY(Player, Z) + 35 THEN Selected = 1 ELSE Selected = 0
      IF HidePieces = 1 AND Selected = 1 THEN
         _PRINTSTRING (DruidX(Player, Z) + 50, DruidY(Player, Z)), STR$(DruidPieces(Player, Z))
      ELSEIF HidePieces = 1 AND Selected = 0 THEN
         _PRINTSTRING (DruidX(Player, Z) + 52, DruidY(Player, Z)), " ?"
      END IF
      IF _MOUSEBUTTON(1) = -1 AND DruidPieces(Player, Z) > 0 AND Selected = 1 THEN
         GOSUB ReleaseButton: IF DruidPieces(Player, Z) = 0 GOTO BoardSpaceInput
         LINE (DruidX(Player, Piece) - 45, DruidY(Player, Piece) - 45)-(DruidX(Player, Piece) + 45, DruidY(Player, Piece) + 45), 1, B: Piece = Z: GOTO ChooseBoardSpace
      END IF
   NEXT

   ' Choose Board Location
   FOR Z = 1 TO 7
      FOR Y = 1 TO 12
         IF _MOUSEX > BoardX(Z, Y) - 35 AND _MOUSEX < BoardX(Z, Y) + 35 AND _MOUSEY > BoardY(Z, Y) - 35 AND _MOUSEY < BoardY(Z, Y) + 35 AND _MOUSEBUTTON(1) = -1 THEN
            GOSUB ReleaseButton: IF DruidSpace(Z, Y) = 0 OR BoardPlayer(Z, Y) > 0 GOTO BoardSpaceInput ELSE Row = Z: Column = Y: GOTO PlaceDruidPiece
         END IF
      NEXT
   NEXT

LOOP
A$ = INKEY$: IF A$ <> "" THEN IF ASC(A$) = 27 AND Full = 0 THEN _FULLSCREEN _SQUAREPIXELS , _SMOOTH: Full = 1 ELSE IF ASC(A$) = 27 THEN _FULLSCREEN _OFF: Full = 0
GOTO BoardSpaceInput

PlaceDruidPiece:
LastRow = Row: LastColumn = Column

' Place Druid Piece on the Board
IF HidePieces = 1 THEN X = 0 ELSE X = 1
BoardPlayer(Row, Column) = Player: BoardPiece(Row, Column) = Piece: DrawDruidPiece BoardX(Row, Column), BoardY(Row, Column), Player, Piece, X

' Update Druid Piece Count
DruidPieces(Player, Piece) = DruidPieces(Player, Piece) - 1: UpdateDruidList: EndTurnButton 1: GetLineTotals Player, Opponent

' Remove Cursor from Chosen Druid Piece
IF HidePieces = 0 THEN
   IF DruidPieces(Player, Piece) = 0 THEN
      LINE (DruidX(Player, Piece) - 45, DruidY(Player, Piece) - 45)-(DruidX(Player, Piece) + 85, DruidY(Player, Piece) + 45), 1, BF
   ELSE
      LINE (DruidX(Player, Piece) - 45, DruidY(Player, Piece) - 45)-(DruidX(Player, Piece) + 45, DruidY(Player, Piece) + 45), 1, B
   END IF
END IF

COLOR 15, 1: font& = _LOADFONT(fontpath$, 25): _FONT font&: _PRINTSTRING (825, 672), STRING$(250, 32): _PRINTSTRING (807, 705), STRING$(250, 32)
_PRINTSTRING (820, 672), "Click <END TURN> Button to End Turn"
_PRINTSTRING (812, 705), "Choose a Megalith Space to Claim a Line"

EndTurnInput:
DO WHILE _MOUSEINPUT

   ' End Turn
   IF _MOUSEX > 965 AND _MOUSEX < 1127 AND _MOUSEY > 595 AND _MOUSEY < 645 AND _MOUSEBUTTON(1) = -1 THEN GOSUB ReleaseButton: GOTO EndTurn

   ' Choose Megalith
   FOR Z = 1 TO 7
      FOR Y = 1 TO 12
         Megalith = MegalithSpace(Z, Y)
         IF _MOUSEX > BoardX(Z, Y) - 25 AND _MOUSEX < BoardX(Z, Y) + 25 AND _MOUSEY > BoardY(Z, Y) - 25 AND _MOUSEY < BoardY(Z, Y) + 25 AND _MOUSEBUTTON(1) = -1 AND Megalith > 0 THEN
            IF EligibleWinner(Megalith) = Player AND LineWinner(Megalith) = 0 THEN GOSUB ReleaseButton: Row = Z: Column = Y: GOTO PlaceMegalith
         END IF
      NEXT
   NEXT

LOOP
A$ = INKEY$: IF A$ <> "" THEN IF ASC(A$) = 27 AND Full = 0 THEN _FULLSCREEN _SQUAREPIXELS , _SMOOTH: Full = 1 ELSE IF ASC(A$) = 27 THEN _FULLSCREEN _OFF: Full = 0
GOTO EndTurnInput

PlaceMegalith:
LineWinner(Megalith) = Player: Megaliths(Player) = Megaliths(Player) - 1: UpdateDruidList
DrawMegaLith BoardX(Row, Column), BoardY(Row, Column), Player

IF Megaliths(Player) = 0 THEN LINE (965, 595)-(1127, 645), 1, BF: DisplayWinner Player ELSE GOTO EndTurnInput

EndTurn:
COLOR 15, 1: _PRINTSTRING (820, 672), STRING$(250, 32): _PRINTSTRING (812, 705), STRING$(250, 32)

SWAP Player, Opponent: GOTO GameLoop

ReleaseButton:
DO WHILE _MOUSEINPUT
   IF _MOUSEBUTTON(1) = 0 THEN RETURN
LOOP
GOTO ReleaseButton

SUB DrawDruidPiece (X, Y, Player, DruidPiece, Show)
   CIRCLE (X, Y), 35, 15: PAINT (X, Y), 15: CIRCLE (X, Y), 32, PieceColor(Player): PAINT (X, Y), PieceColor(Player)
   fontpath$ = ENVIRON$("SYSTEMROOT") + "\fonts\Segoeuib.ttf"
   IF Show = 1 THEN
      COLOR 15, PieceColor(Player): font& = _LOADFONT(fontpath$, 43): _FONT font&
      _PRINTSTRING (X - 24, Y - 20), STR$(DruidPiece)
   END IF
END SUB

SUB DrawMegaLith (X, Y, Player)
   fontpath$ = ENVIRON$("SYSTEMROOT") + "\fonts\Segoeuib.ttf"
   DIM AS _BYTE W: W = PieceColor(Player)
   LINE (X - 18, Y - 25)-(X + 18, Y - 25), 15: LINE (X - 18, Y + 25)-(X + 18, Y + 25), 15: LINE (X - 25, Y - 18)-(X - 25, Y + 18), 15: LINE (X + 25, Y - 18)-(X + 25, Y + 18), 15
   CIRCLE (X - 18, Y - 18), 7, 15, 1.3, 3.1: CIRCLE (X + 18, Y - 18), 7, 15, 0, 1.6: CIRCLE (X - 18, Y + 18), 7, 15, 2.9, 4.8: CIRCLE (X + 18, Y + 18), 7, 15, 4.4, 0: PAINT (X, Y), W, 15
   COLOR 5, W: font& = _LOADFONT(fontpath$, 35): _FONT font&: _PRINTSTRING (X - 19, Y - 15), STR$(Player)
END SUB

SUB UpdateDruidList
   DIM AS _BYTE Z, Y
   fontpath$ = ENVIRON$("SYSTEMROOT") + "\fonts\Segoeuib.ttf"
   COLOR 5, 1: font& = _LOADFONT(fontpath$, 35): _FONT font&
   FOR Z = 1 TO 2
      FOR Y = 1 TO 6
         IF HidePieces = 1 THEN Druid$ = " ?" ELSE IF DruidPieces(Z, Y) > 0 THEN Druid$ = STR$(DruidPieces(Z, Y)) ELSE Druid$ = "  "
         _PRINTSTRING (DruidX(Z, Y) + 50, DruidY(Z, Y)), Druid$
      NEXT
   NEXT
   COLOR 5, 1: _PRINTSTRING (898, 602), STR$(Megaliths(1)): _PRINTSTRING (1265, 602), STR$(Megaliths(2))
END SUB

SUB EndTurnButton (Show)
   fontpath$ = ENVIRON$("SYSTEMROOT") + "\fonts\Segoeuib.ttf"
   font& = _LOADFONT(fontpath$, 25): _FONT font&
   CIRCLE (975, 605), 10, 3, 1.3, 3.1: CIRCLE (1117, 605), 10, 3, 0, 1.6: CIRCLE (975, 635), 10, 3, 2.9, 4.8: CIRCLE (1117, 635), 10, 3, 4.4, 0
   LINE (975, 595)-(1117, 595), 3: LINE (975, 645)-(1117, 645), 3: LINE (965, 605)-(965, 635), 3: LINE (1127, 605)-(1127, 635), 3: PAINT (975, 605), 3
   IF Show = 1 THEN COLOR 0, 3 ELSE COLOR 15, 3
   _PRINTSTRING (983, 610), "END TURN"
END SUB

SUB GetLineTotals (Player, Opponent)
   DIM AS _BYTE Row, Column, X, Y, Z, HighestPiece(2), LineCount(15)

   ' Get Piece Count for Each Line
   FOR Z = 1 TO 15
      LineCount(Z) = 0
      FOR Y = 1 TO LineSize(Z)
         IF BoardPlayer(LineRow(Z, Y), LineColumn(Z, Y)) > 0 THEN LineCount(Z) = LineCount(Z) + 1
      NEXT
   NEXT

   ' Uncover Pieces
   IF HidePieces = 1 THEN
      FOR Z = 1 TO 15
         IF LineComplete(Z) = 0 THEN
            IF LineCount(Z) = LineSize(Z) THEN
               LineComplete(Z) = 1
               FOR Y = 1 TO LineSize(Z)
                  Row = LineRow(Z, Y): Column = LineColumn(Z, Y)
                  IF DruidShown(Row, Column) = 0 THEN
                     DruidShown(Row, Column) = 1
                     DrawDruidPiece BoardX(Row, Column), BoardY(Row, Column), BoardPlayer(Row, Column), BoardPiece(Row, Column), 1
                  END IF
               NEXT
            END IF
         END IF
      NEXT
   END IF

   ' Get Player Line Totals
   FOR Z = 1 TO 15
      IF EligibleWinner(Z) = 0 AND LineWinner(Z) = 0 THEN
         LineTotal(1, Z) = 0: LineTotal(2, Z) = 0: X = 0
         FOR Y = 1 TO LineSize(Z)
            Row = LineRow(Z, Y): Column = LineColumn(Z, Y): IF Row = LastRow AND Column = LastColumn THEN X = 1
            IF BoardPlayer(Row, Column) = 1 THEN LineTotal(1, Z) = LineTotal(1, Z) + BoardPiece(Row, Column)
            IF BoardPlayer(Row, Column) = 2 THEN LineTotal(2, Z) = LineTotal(2, Z) + BoardPiece(Row, Column)
         NEXT
         IF LineCount(Z) = LineSize(Z) THEN
            LineComplete(Z) = 1
            IF LineTotal(1, Z) > LineTotal(2, Z) THEN EligibleWinner(Z) = 1
            IF LineTotal(1, Z) < LineTotal(2, Z) THEN EligibleWinner(Z) = 2
            IF LineTotal(1, Z) = LineTotal(2, Z) AND X = 1 THEN EligibleWinner(Z) = Opponent
         END IF
      END IF
   NEXT

   ' Get Player's Highest Available Druid Piece
   HighestPiece(1) = 0: HighestPiece(2) = 0
   FOR Z = 1 TO 6
      IF DruidPieces(1, Z) > 0 THEN HighestPiece(1) = Z
      IF DruidPieces(2, Z) > 0 THEN HighestPiece(2) = Z
   NEXT

   ' Get Line Eligible Players
   FOR Z = 1 TO 15
      IF EligibleWinner(Z) = 0 AND LineWinner(Z) = 0 THEN
         LineTotal(1, Z) = 0: LineTotal(2, Z) = 0: X = 0
         FOR Y = 1 TO LineSize(Z)
            Row = LineRow(Z, Y): Column = LineColumn(Z, Y):
            IF BoardPlayer(Row, Column) > 0 THEN LineTotal(BoardPlayer(Row, Column), Z) = LineTotal(BoardPlayer(Row, Column), Z) + BoardPiece(Row, Column)
         NEXT
         IF LineCount(Z) = LineSize(Z) - 1 THEN IF LineTotal(Player, Z) >= LineTotal(Opponent, Z) + HighestPiece(Opponent) THEN EligibleWinner(Z) = Player
      END IF
   NEXT

END SUB

SUB DisplayWinner (Player)
   fontpath$ = ENVIRON$("SYSTEMROOT") + "\fonts\Segoeuib.ttf"
   COLOR 15, 1: font& = _LOADFONT(fontpath$, 25): _FONT font&
   _PRINTSTRING (815, 672), STRING$(250, 32): _PRINTSTRING (807, 705), STRING$(250, 32)
   _PRINTSTRING (910, 672), "Player " + STR$(Player) + " is the Winner!"
   _PRINTSTRING (876, 705), "Play Another Game? ( Y or N )"
   GetYorN: A$ = UCASE$(INKEY$): IF A$ = "" GOTO GetYorN
   IF ASC(A$) = 27 AND FullScreen = 0 THEN FullScreen = -1: _FULLSCREEN _SQUAREPIXELS , _SMOOTH ELSE IF ASC(A$) = 27 THEN FullScreen = 0: _FULLSCREEN _OFF
   IF A$ = "Y" THEN RUN ELSE IF A$ = "N" THEN SYSTEM ELSE GOTO GetYorN
END SUB

Print this item

  Math and Physics stuff
Posted by: SMcNeill - 05-28-2024, 07:49 AM - Forum: General Discussion - Replies (1)

https://ipnp.cz/~kvasil/
Seems to be owned by a Professor of Mathematics and Physics at Charles University in Prague, lots of cool stuff in here, even if I understand close to zero!.

^ was shared by a friend, with me, so I thought I'd pass along the resources for anyone interested.  Seems to b tons of scanned in pages of hand written notes, lectures, projects, and all look like fancy scribbles to me!  Big Grin

Print this item

  Viewing SUBs in IDE
Posted by: TerryRitchie - 05-27-2024, 06:24 PM - Forum: General Discussion - No Replies

Just a quick idea/suggestion that maybe could be incorporated into the IDE at a later date.

It would be great if when viewing the SUBs in the IDE (View-->SUBs...  F2) the subroutines and functions from included libraries would also be listed. If one of the included subs or functions is chosen from the list a second IDE appears with that library's code scrolled down to that sub/function.

Within the SUBs listing box that appears showing Program Items perhaps the subroutines and functions contained in library files could be a different color to delineate them from the local subroutines and functions.

As a bonus pressing F1 within the code on an external library subroutine/function would act the same as pressing F1 on a local one.

Just a thought. Since the introduction of $INCLUDEONCE I have been breaking my libraries up into smaller library files making it easier to add just the routines I need to new projects. I often include the usage documentation directly into the include file within the subroutines and functions. It would be great if I could use F2 to select and view these include files at will in a secondary IDE.

I use RhoSigma's modified Notepad++ to do this now but sometimes I'll have 30+ documents open at a time and finding the right one for a quick peek at documentation takes a bit of time.

Print this item

  Extended KotD #12 and #13: $EMBED and _EMBEDDED$
Posted by: SMcNeill - 05-27-2024, 01:42 AM - Forum: Keyword of the Day! - Replies (3)

Moving ever backwards in our list of "new" QB64PE-releases, we count down to v3.10 and the following two commands which work together:
https://qb64phoenix.com/qb64wiki/index.php/$EMBED
https://qb64phoenix.com/qb64wiki/index.php/EMBEDDED$

Now let me say two very important things about these commands:
1) These are probably in my top 10 of new commands, and are some of the most useful commands we've ever added into the language.
2) These are utterly useless commands for many people, in most use cases.

Now take a moment to let those two statements sink in.  In fact, go back and reread them once again.  And then ask yourselves, "Whuuttt??  Is Steve drunk again??"


No.  Steve is not drunk.  Steve meant exactly what he wrote above.   

Now, let me explain why I feel this way with these commands.  Big Grin

First, folks need to understand what the purpose of $EMBED and _EMBEDDED$ are, for their programs -- and that purpose is to embed files/data/resources into the compiled EXE.

EMBED STUFF INTO THE EXE!!!!  <-- Let's be certain to highlight this point.

And it's this whole highlighted point that makes me say that this is one of the most useful features/keywords to be added to the language in a long time.  Lots of folks have worked on ways to embed data into their programs, such as Dav's BASFILE routines. 

Now, let's take a moment and step sideways here and talk about @Dav 's little BASFILE routine.  What's it do for us exactly, and how would one make use of it?

Dav's routine takes a resource and turns it into DATA statements, which you then paste directly into your code.  There's then a routine which converts those DATA statements back into the given resource (such as a font file, image, or sound file), which you can use in your program.  The original resource doesn't have to be anywhere on your drive, or exist at all, once those DATA statements are pasted into the source -- they're 100% embedded into the BAS file itself.

And that's useful as heck!!  Convert a file, paste the contents into your source, and then you can share it via the forums or wherever and not have to worry about including any additional files for folks to download.

@Dav -- You rock, man!!  (And not just cause you're a musician and litterly rock either!  Big Grin )



But...  there's a slight problem with Dav's method of doing things -- and that's simple CODE BLOAT.  Embed a couple of large fonts.  Then embed a couple of large sound files.  Add in a large image file or three.  Suddenly you've got 300,000 lines of DATA statements to navigate past and work around and to TRY and share wherever you want to share them.  The IDE is going to get laggy and bog down trying to process all those lines.  The forums is going to stick out its tongue and say, "Nuh uh!  You've exceeded the limit for any post!"  Notice that even in the title, Dav mentions:  "Converts small files to BAS code."

Large files are going to run into all sorts of issues over time with such a method...

...So that's NOT how QB64PE does things with $EMBED and _EMBEDDED$!!

Take a moment to understand Dav's process, and then take a moment to learn how QB64PE does things:

QB64PE lets you type in a single line to embed data into the EXE: 
Code: (Select All)
$EMBED:'source\peLogo.png','bigImg'
$EMBED:'source\qb64pe.png','smallImg'

SCREEN _NEWIMAGE(640, 480, 32)

bi& = _LOADIMAGE(_EMBEDDED$("bigImg"), 32, "memory")
si& = _LOADIMAGE(_EMBEDDED$("smallImg"), 32, "memory")

_PUTIMAGE (140, 180), bi&
_PUTIMAGE (410, 230), si&

_FREEIMAGE si&
_FREEIMAGE bi&

END

As per our wiki example, you can see the two $EMBED statements above -- both are referring to EXTERNAL data files.  "source\peLogo.png" and "source\qb4pe.png"...

... and this is why I say $EMBED is utterly useless for 99.98765% of most people and use cases.

Folks normally tend to include their source files with any EXE files which they distribute via the forums, or github, or other means.  QB4PE compiled EXEs are small, unregistered EXE files, and as such, will likely trigger various antivirus warnings for folks.  The way to bypass that suspicion that an EXE might be malicious or corrupted, is simply to share the BAS file and source, and then let folks compile it themselves.

Add allowing folks to compile for themselves has the added bonus of making the program cross-platform independent in most cases.  A guy on Linux can compile it to run on his version of Linux.  Someone with a Raspberry Pi can compile it to run on his Pi.  Windows folks can compile the source to run on Windows...

And if you're going to share the source files, you STILL HAVE TO SHARE THOSE RESOURCE FILES!!!

QB64PE pulls from those external resource files, and embeds them into the EXE at compile time.  Without them, folks aren't going to be able to access the data and compile the program properly.

$EMBED and _$MBEDDED$ require that the resources exist, be findable, and be available at compile time -- unlike Dav's method which converts those resources and embeds them directly into the source BAS file itself.



And thus, my statement that $EMBED is going to be worthless for most folks, in most use cases. 

IF you're going to be sharing the BAS source, and you want others to be able to build the EXE for themselves, then there's not much point to $EMBED.  Just pull in the external resource with _LoadFont, _SndLoad, _LoadFont, or whatever other command you need.  There's no real reason to bloat the EXE by cramming that file into it, while also having that file sitting in a folder right there beside the EXE!

The only real time where one wants to use $EMBED and _EMBEDDED$ is when they're wanting to embed data into the EXE, *and NOT share the source with that EXE*!

And, in that instance, $EMBED and _EMBEDDED$ are absolutely 100%-certified gold commands!!

Now, I know I haven't went over how to use these commands much here, but that's simply because @RhoSigma made their usage so simple for us.  The wiki covers usage quite well, and I don't think there's too much I can offer for folks over the simple example below:

Code: (Select All)
$EMBED:'source\peLogo.png','bigImg'
$EMBED:'source\qb64pe.png','smallImg'

SCREEN _NEWIMAGE(640, 480, 32)

bi& = _LOADIMAGE(_EMBEDDED$("bigImg"), 32, "memory")
si& = _LOADIMAGE(_EMBEDDED$("smallImg"), 32, "memory")

_PUTIMAGE (140, 180), bi&
_PUTIMAGE (410, 230), si&

$EMBED <the reource to be embedded into your program> , <a handle to distinguish it for later use>

then later....    _EMBEDDED$("<the handle you used to distinguish that file>")

The first designates the file to embed into the QB64PE program.
The second assigns the contents of that file to a string, which you can then do whatever you need to do with it.

They're simple as heck to use.  The trick is knowing IF using them is going to help you do what you're trying to do in the long run, or not.

If you're looking to embed the resource into the EXE so you can distribute it as a stand-alone program, then, "YES!! YOU WANT TO USE THEM!!"

If you're going to share the BAS source and let folks compile the program for themselves, so you can be platform-independent and such, then you might want to rethink using $EMBED.  You'll still need to share the resource files so folks can embed that external data into their compiled EXE.  Why not just use that external data directly and skip packing a second copy of that resource into the EXE when it's right there beside it with the BAS file??

Something to think about for folks, I hope.  Wink

Print this item

  Threading in QB64pe (again)
Posted by: justsomeguy - 05-27-2024, 12:58 AM - Forum: Works in Progress - Replies (19)

Hello all,

I've been revisiting my earlier attempt at threading in QB64pe. You can look at the old thread at https://qb64forum.alephc.xyz/index.php?topic=3865.0 Thanks to guys that responded to that thread and helped me. 

I'm happy to say I made a bit of progress. I managed to get two separate free running threads to run concurrent with my main QB64 program. They don't do much, but they show its possible.

In order to get it working I had to cheat and use a c header file to make a wrapper for pthreads. This makes the declarations easier.

Code: (Select All)
// pthreadGFXTest.h
// Threading Header
#include "pthread.h"
// Only needed for the SIGTERM Constant
#include <signal.h>
// Initialize Threads
pthread_t thread0;
pthread_t thread1;
// Easy way to determine if a thread is running
bool threadRunning0 = false;
bool threadRunning1 = false;
// Setup Mutexes for each of the threads.
static pthread_mutex_t mutex0;
static pthread_mutex_t mutex1;
// QB's names for the threaded Subs
// You can locate these in your ''qb64pe/internal/temp'' folder.
// I found these in the 'main.txt'
void SUB_LINES();
void SUB_CIRCLES();
// wrap the subs so that you can easily get the void* for pthread
void* RunLines(void *arg){
    SUB_LINES();
}
void* RunCircles(void *arg){
    SUB_CIRCLES();
}
// These are the commands that are accessed by you program
void invokeLines(){
    if (!threadRunning0) {
        int iret = pthread_create( &thread0, NULL, RunLines, NULL);
        pthread_mutex_init(&mutex0, NULL);
        threadRunning0 = true;
    }
}
void invokeCircles(){
    if (!threadRunning1) {
        int iret = pthread_create( &thread1, NULL, RunCircles, NULL);
        pthread_mutex_init(&mutex1, NULL);
        threadRunning1 = true;
    }
}
void joinThread0(){
    pthread_join(thread0,NULL);
    threadRunning0 = false;
}
void joinThread1(){
    pthread_join(thread1,NULL);
    threadRunning1 = false;
}
void exitThread(){
    pthread_exit(NULL);
}
void killThread0(){
    if (threadRunning0) {
        int iret = pthread_kill(thread0, SIGTERM);
    }
}
void killThread1(){
    if (threadRunning1) {
        int iret = pthread_kill(thread1, SIGTERM);
    }
}
void lockThread0(){
    pthread_mutex_lock(&mutex0);
}
void unlockThread0(){
    pthread_mutex_unlock(&mutex0);
}
void lockThread1(){
    pthread_mutex_lock(&mutex1);
}
void unlockThread1(){
    pthread_mutex_unlock(&mutex1);
}

The test program draws lines in one thread and circles in another thread. You start and stop the threads by pressing '1' and '2' and 'ESC' quits.


Code: (Select All)
'***********************************************************************************
' Proof of concept threading in QB64pe.
' by justsomeguy
'***********************************************************************************

' Thread Library Declaration
DECLARE LIBRARY "./pthreadGFXTest"
  SUB invokeLines ' start Lines thread
  SUB invokeCircles ' start Circles thread
  SUB joinThread0 ' wait til thread is finished
  SUB joinThread1 ' wait til thread is finished
  SUB exitThread ' must be called as thread exits
  SUB killThread0 ' kill the thread
  SUB killThread1 ' kill the thread
  SUB lockThread0 ' mutex lock
  SUB unlockThread0 ' mutex unlock
  SUB lockThread1 ' mutex lock
  SUB unlockThread1 ' mutex unlock
END DECLARE

' Global variables
DIM SHARED AS INTEGER q0, q1 ' quit signals
DIM AS STRING ky

' Setup screen
_TITLE "Thread test"
SCREEN _NEWIMAGE(1024, 768, 32)
_FONT 8
CLS

' Fire up freerunning threads
invokeCircles
invokeLines

' Campout in an infinite loop
DO
  ky = INKEY$
  LOCATE 1, 1
  PRINT "Lines are drawn on one thread, Circles are drawn in a second thread."
  PRINT "Press '1' to toggle the Line drawing thread. "
  PRINT "Press '2' to toggle the Circle drawing thread."
  PRINT "Press 'ESC' to exit."

  IF ky = "1" THEN
    q0 = NOT q0
    IF q0 THEN
      joinThread0
    ELSE
      invokeLines
    END IF
  END IF

  IF ky = "2" THEN
    q1 = NOT q1
    IF q1 THEN
      joinThread1
    ELSE
      invokeCircles
    END IF
  END IF

  ' Quit the whole program
  IF ky = CHR$(27) THEN q0 = -1: q1 = -1: joinThread0: joinThread1: SYSTEM
LOOP


'***********************************************************************************
' Threaded
'***********************************************************************************

SUB lines ()
  ' Free running loop
  DO
    ' lock a mutex, just to be safe
    lockThread0
    ' Do something
    LINE (RND * _WIDTH, RND * _HEIGHT)-(RND * _WIDTH, RND * _HEIGHT), _RGB32(RND * 255, RND * 255, RND * 255)
    ' unlock mutex
    unlockThread0
    ' do I need to jump out?
  LOOP UNTIL q0 = -1
  ' Must call exitThread when leaving, so that joinThread works.
  exitThread
END SUB

SUB circles ()
  ' Free running loop
  DO
    ' lock a mutex, just to be safe
    lockThread1
    ' Do something
    CIRCLE (RND * _WIDTH, RND * _HEIGHT), RND * 50, _RGB32(RND * 255, RND * 255, RND * 255)
    ' unlock mutex
    unlockThread1
    ' do I need to jump out?
  LOOP UNTIL q1 = -1
  ' Must call exitThread when leaving, so that joinThread works.
  exitThread
END SUB


My goal, is to get my 2d physics engine to reside in a separate thread and have it free running computing collisions and motion while the main thread handles I/O and other logic.

I'm using QB64pe 3.11.0. I tested this on Linux Mint, MacOS and Windows 10. On windows I had to add '-pthread' to compiler settings and '--static' to the linker settings.

[Image: win-Comp-Setting.png]

To get this running on your computer, copy the header to your favorite text editor and save the file under 'pthreadGFXTest.h' Then copy the source to the same directory as the header and make sure your compiler and linker settings are correct. I'm not sure if its necessary, but I save my EXE to the source folder.

Beware that if you decide try playing around with the code, that it could crash in some wild ways. Error messages will not make sense, and it might run a bit and lockup for unknown reasons. QB64pe is not meant to be run like this, so there will not be much help if you try.

Print this item

  Saying Hello
Posted by: marbac74 - 05-26-2024, 05:55 PM - Forum: General Discussion - Replies (27)

Hi Everyone,
my name is Marco from Italy, I'm 49 and I am a teacher (History/Philosophy). I also like programming as an hobby, I like playing games on the PC. I have a MacBook with M2 processor and Sonoma as an OS. I'm following Terry's tutorial on how to learn QB64 and I'm really enjoying it. I registered yesterday to be more connected to the community of people who work at developing QB64PE in case of issues, questions and similar things. Up to now I downloaded two excellent games with which I played a little bit lately: Galaga, which brought so many memories of playing arcade games, and Tic Tac Toe Rings, which is in many respects a peculiar and beautiful game. I'll stay tuned and I hope I'll be making some progress with my QBasic and QB64 proficiency.
Thanks a lot for now,
Marco

Print this item

  Delete records in a random file
Posted by: Kernelpanic - 05-26-2024, 05:37 PM - Forum: Help Me! - Replies (2)

The example is a random file that is connected to an index file. A kind of ISAM file management. The practical sense should be:
One cannot simply delete a record from a random file; For example, the data record with the item number (key) 2345 should be deleted.

To make this possible, entry 2345 in the index file must first be deleted, but the data record number, which is also saved and refers to the random file, remains intact. By deleting entry 2345 in the index file, all occupied entries in the index file move down, while the "deleted" entry with the data record number is stored as the first free field above it. The effect is that if a new data record is written, then the "deleted" data record is overwritten. In this respect, a random data record has been deleted after all. - So much for the theory.

The template for the example is from a book. There is no error message, but the result is not convincing!  Sad  I can't figure out why nothing useful is displayed. But there must be a logical error somewhere.

Maybe someone could look at the whole thing and find the error. Thanks!

Create random file:

Code: (Select All)

'Randomdatei mit Index, Hueckstaedt S. 321 - 13. Mai 2024

$Console:Only

Option _Explicit

Type Warenposten
  nummer As String * 4
  artikel As String * 10
  preis As Double
End Type

'Variable von Warenposten
Dim datensatz As Warenposten

Declare Sub AddiereSchluessel(i As Integer, nummer As String, index() As String)

Dim As Integer maxAnzahl, i, k

'Indexdatei fuer maximal 20 Datensaetze anlegen
maxAnzahl = 10
Dim As String index(maxAnzahl)
For i = 1 To maxAnzahl
  index(i) = "9999":
Next i

'Randomdatei anlegen bzw. oeffnen.
'Len muss dem laengsten Datensatz entsprechen
Open "RandomIndex.dat" For Random As #5 Len = Len(datensatz)

i = 1
Do
  'Daten aus Datazeile einlesen
  Read datensatz.nummer
  Read datensatz.artikel
  Read datensatz.preis

  'Datensatz in Datei schreiben
  Put #5, i, datensatz

  'Index aktuallisiren
  Call AddiereSchluessel(i, datensatz.nummer, index())

  'Zeiger erhoehen
  i = i + 1

Loop Until datensatz.nummer = "9999"

'Datenbank schliesen
Close #5

'Indexdatei schreiben
Open "RandomIndex.ind" For Output As #3
For k = 1 To maxAnzahl
  Print #3, index(k)
Next k

Close #3
End
:

Data 1233,Lenker,245.99
Data 0891,Lampe,188.90
Data 1122,Telegabel,499.95
Data 2301,Tank,377.50
Data 1755,Sitzbank,321.00
Data 9999,"",0

Sub AddiereSchluessel (i As Integer, nummer As String, index() As String)

  Dim As String zahl, einfuegen
  Dim As Integer k

  zahl = " ": RSet zahl = Str$(i)

  k = 1: einfuegen = nummer + zahl

  Do While einfuegen > index(k) And k <= i
    k = k + 1
  Loop

  Do While k <= i
    Swap einfuegen, index(k)
    k = k + 1
  Loop

  index(k) = einfuegen
End Sub

Output of the data
Code: (Select All)

'Die erstellte Randomsatei mit Index lesen - Hueck S.324 - 26. Mai 2024

Cls

Option _Explicit

Type Warenposten
  nummer As String * 4
  artikel As String * 10
  preis As Double
End Type

'Variable von Warenposten
Dim datensatz As Warenposten

Dim As Integer maxAnzahl, i, k, z

'Indexdatei einlesen
maxAnzahl = 10
Dim As String index(maxAnzahl)

i = 0
Open "RandomIndex.ind" For Input As #3
Do While Not EOF(3) And index(i) <> "9999"
  i = i + 1
  Input #3, index(i)
Loop
Close #3

k = i - 1

'Randomdatei oeffnen
Open "RandomIndex.dat" For Random As #5 Len = Len(datensatz)
Print "Nr.    Artikel              Preis"
Print

i = 1
Do While Not i > k
  z = Val(Right$(index(i), 4))

  'Datensatz lesen
  Get #5, z, datensatz

  'Dateiende Kennzeichnung
  If datensatz.nummer = "9999" Then Exit Do

  'Datensatz ausgeben
  Print Using "\  \"; datensatz.nummer;
  Print "    ";
  Print Using "\              \"; datensatz.artikel;
  Print "    ";
  Print Using "####.##"; datensatz.preis

  'Zeiger erhoehen
  i = i + 1
Loop

Close #5

End 'Hauptprogramm

[Image: Random-Index-Datei-ausgeben2024-05-26.jpg]

Print this item

  C++ in QB?
Posted by: CletusSnow - 05-26-2024, 04:11 PM - Forum: Help Me! - Replies (6)

I think I read somewhere that it is possible to use C++ code within a QB program. But unfortunately I can't find any information about it.
Is it possible, and if so, how?

Print this item

  Extended KotD #11: _FULLPATH$
Posted by: SMcNeill - 05-24-2024, 11:09 PM - Forum: Keyword of the Day! - Replies (1)

Finally, a KotD that doesn't require a lot of writing about from me, nor a lot of reading about for you guys.  This one is extremely simple to learn, use, and implement -- https://qb64phoenix.com/qb64wiki/index.php/FULLPATH$

FullPath$, quite simply, returns the full path of whatever file you give it.   For example:

Code: (Select All)
Print _CWD$
Print _FullPath$("../../")

My QB64 stuff is located on my drive D:, inside the folders and subfolders: D:\QB64 Stuff\Official Phoenix Edition\QB64pe-fork

Running the above, on my laptop, and the result would be:

D:\QB64 Stuff\Official Phoenix Edition\QB64pe-fork
D:\QB64 Stuff

The _CWD$ gives the current workding directory, while the _FullPath$ gives the full path of that "../../" resolved path, which is "2 folders above the current one".

I honestly don't know what else to say about it.  You give it a relative path, or a file with a hardcoded path, and the command returns the resolved full path back to you.  That's all there is to this little command.   Quick.  Simple.  Easy to incorporate into existing code.   What more could you ever want?  Big Grin

Print this item

Question why are these API declarations causing compilation to fail?
Posted by: madscijr - 05-24-2024, 10:35 PM - Forum: Help Me! - Replies (2)

The program "readmicesub35.bas" in the code listing below is giving a "c++ compilation failed" error. 

"compilelog.txt":

Code: (Select All)
internal\c\c_compiler\bin\c++.exe  -std=gnu++17 -fno-strict-aliasing -Wno-conversion-null -DGLEW_STATIC -DFREEGLUT_STATIC -Iinternal\c\libqb/include -Iinternal\c/parts/core/freeglut/include -Iinternal\c/parts/core/glew/include -DDEPENDENCY_SOCKETS -DDEPENDENCY_NO_PRINTER -DDEPENDENCY_NO_ICON -DDEPENDENCY_NO_SCREENIMAGE -DDEPENDENCY_AUDIO_MINIAUDIO internal\c/qbx.cpp -c -o internal\c/qbx.o
In file included from internal\c/qbx.cpp:739:
internal\c/../temp/regsf.txt:62:11: error: redefinition of 'HINSTANCE__* DLL_user32'
   62 | HINSTANCE DLL_user32=NULL;
      |           ^~~~~~~~~~
internal\c/../temp/regsf.txt:54:11: note: 'HINSTANCE__* DLL_user32' previously defined here
   54 | HINSTANCE DLL_user32=NULL;
      |           ^~~~~~~~~~
mingw32-make: *** [Makefile:402: internal\c/qbx.o] Error 1


I did a file comparison and the only difference with the previous version that compiles & runs are a few short lines:
[Image: readmisesub34c-vs-35.png]


New lines causing  "readmicesub35.bas" compilation to fail:

Code: (Select All)
273 ' FOR CONTROLLING WINDOW ON TOP, ETC.
274 ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
275 Declare Dynamic Library "user32"
276     'Function FindWindowA%& (ByVal lpClassName%&, Byval lpWindowName%&)
277     Function SetWindowPos& (ByVal hWnd%&, Byval hWndInsertAfter%&, Byval X&, Byval Y&, Byval cx&, Byval cy&, Byval uFlags~&)
278     Function GetForegroundWindow%&
279 End Declare
280
281 Declare Dynamic Library "kernel32"
282     Function GetLastError~& ()
283 End Declare

586     SetWindowOpacity hwndMain, cTransparent

601     ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
602     ' MOVE WINDOW TO TOP
603     ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
604     '' GET WINDOW HANDLES
605     'hWndThis = _WindowHandle ' FindWindowA(0, _OFFSET(t))
606     hWndTop = GetForegroundWindow%& ' find currently focused process handle

608     ' GET FOCUS
609     If hwndMain <> hWndTop Then
610         _ScreenClick 240, 240 ' add 40 to x and y to focus on positioned window
611     End If

613     ' MOVE TO TOP
614     If SetWindowPos(hwndMain, HWND_TOPMOST, 200, 200, 0, 0, SWP_NOSIZE Or SWP_NOACTIVATE) = 0 Then
615         'sNextError = "SetWindowPos failed. 0x" + LCase$(Hex$(GetLastError))
616         m_sError = "SetWindowPos failed. 0x" + LCase$(Hex$(GetLastError))
617     End If

I added those API calls to try to force the ReadMiceSub window to keep the focus, and also make the window transparent (invisible) so that the ReadMiceMain window is what the user sees, but when they move their mouse, the ReadMiceSub can read the mice values since it has the focus. Steffan-68 provided some sample code which I had success with and I wanted to use that for this program.


If anyone can shed some light on why those lines are causing it to fail, that would be great, because I just used those same API functions in another program (see here) and they worked fine.
Why won't it compile in a different program?  Huh

ANY help would be much appreciated because I've been banging my head against a wall for the past hour... 


Below are the 2 files - the readmicesub35 that won't compile, and the previous version readmicesub34c that works. 

(The attached ZIP file has everything for the project). 


"ReadMiceSub35.bas" compilation failed:
Code: (Select All)
' ################################################################################################################################################################
' Multimouse sub-program "ReadMiceSub.bas"
' ################################################################################################################################################################

' -------------------------------------------------------------------------------
' ABOUT
' -------------------------------------------------------------------------------
' (Subprogram for READMICEMAIN.BAS, see that for more info.)

Option Explicit
_Title "readmice"
$NoPrefix
'$Console:Only
'Console Off

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' BEGIN CONSTANTS
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Const cProgName = "ReadMiceSub"
Const FALSE = 0
Const TRUE = Not FALSE

Const cInvisible = 0
Const cTransparent = 160
Const cVisible = 255

' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
' FOR CONTROLLING WINDOW ON TOP, ETC.
Const SWP_NOSIZE = &H0001 'ignores cx and cy size parameters
'Const SWP_NOMOVE = &H0002 'ignores x and y position parameters
'Const SWP_NOZORDER = &H0004 'keeps z order and ignores hWndInsertAfter parameter
'Const SWP_NOREDRAW = &H0008 'does not redraw window changes
Const SWP_NOACTIVATE = &H0010 'does not activate window
'Const SWP_FRAMECHANGED = &H0020
'Const SWP_SHOWWINDOW = &H0040
'Const SWP_HIDEWINDOW = &H0080
'Const SWP_NOCOPYBITS = &H0100
'Const SWP_NOOWNERZORDER = &H0200
'Const SWP_NOSENDCHANGING = &H0400
'Const SWP_DRAWFRAME = SWP_FRAMECHANGED
'Const SWP_NOREPOSITION = SWP_NOOWNERZORDER
'Const SWP_DEFERERASE = &H2000
'Const SWP_ASYNCWINDOWPOS = &H4000
Const HWND_TOP = 0 'window at top of z order no focus
Const HWND_BOTTOM = 1 'window at bottom of z order no focus
Const HWND_TOPMOST = -1 'window above all others no focus unless active
Const HWND_NOTOPMOST = -2 'window below active no focus

' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
' FOR RAW INPUT API
Const CS_HREDRAW = &H0002
Const CS_VREDRAW = &H0001

Const IDI_APPLICATION = 32512
Const IDC_ARROW = 32512
Const COLOR_WINDOW = 5

Const WS_OVERLAPPED = &H00000000
Const WS_CAPTION = &H00C00000
Const WS_SYSMENU = &H00080000
Const WS_THICKFRAME = &H00040000
Const WS_MINIMIZEBOX = &H00020000
Const WS_MAXIMIZEBOX = &H00010000
Const WS_CHILD = &H40000000
Const WS_VISIBLE = &H10000000
Const WS_OVERLAPPEDWINDOW = WS_OVERLAPPED Or WS_CAPTION Or WS_SYSMENU Or WS_THICKFRAME Or WS_MINIMIZEBOX Or WS_MAXIMIZEBOX
Const CW_USEDEFAULT = &H80000000

Const WM_DESTROY = &H0002
Const WM_INPUT = &H00FF

Const SW_SHOW = 5

Const RID_INPUT = &H10000003

Const RIM_TYPEMOUSE = 0 ' Raw input comes from the mouse.
Const RIM_TYPEKEYBOARD = 1 ' Raw input comes from the keyboard.
Const RIM_TYPEHID = 2 ' Raw input comes from some device that is not a keyboard or a mouse.

Const MOUSE_MOVE_RELATIVE = &H00
Const MOUSE_MOVE_ABSOLUTE = &H01
Const MOUSE_VIRTUAL_DESKTOP = &H02
Const MOUSE_ATTRIBUTES_CHANGED = &H04
Const MOUSE_MOVE_NOCOALESCE = &H08

Const WM_MOUSEMOVE = &H0200

Const WM_PAINT = &H000F

Const DT_CENTER = &H00000001

' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
' MIN/MAX VALUES FOR MOUSE TEST
Const cMinX = 1
Const cMaxX = 80
Const cMinY = 1
Const cMaxY = 30 ' 24
Const cMinWheel = 0
Const cMaxWheel = 255

' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
' CONSTANT FOR 2ND DIMENSION OF arrFile ARRAY
Const cFileName = 0
Const cFileData = 1

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' END CONSTANTS
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' BEGIN UDTs
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Type RAWINPUTDEVICE
    As Unsigned Integer usUsagePage, usUsage
    As Unsigned Long dwFlags
    As Offset hwndTarget ' <- WHAT IS Offset VS _Offset ?
End Type

Type RAWINPUTDEVICELIST
    As Offset hDevice
    As Unsigned Long dwType
    $If 64BIT Then
        As String * 4 alignment
    $End If
End Type

Type POINT
    As Long x, y
End Type

Type MSG
    As Offset hwnd
    As Unsigned Long message
    As Unsigned Offset wParam
    As Offset lParam
    As Long time
    As POINT pt
    As Long lPrivate
End Type

Type WNDCLASSEX
    As Unsigned Long cbSize, style
    As Offset lpfnWndProc
    As Long cbClsExtra, cbWndExtra
    As Offset hInstance, hIcon, hCursor, hbrBackground, lpszMenuName, lpszClassName, hIconSm
End Type

Type RECT
    As Long left, top, right, bottom
End Type

Type PAINTSTRUCT
    As Offset hdc
    As Long fErase
    $If 64BIT Then
        As String * 4 alignment
    $End If
    As RECT rcPaint
    As Long fRestore, fIncUpdate
    As String * 32 rgbReserved
End Type

Type RAWINPUTHEADER
    As Unsigned Long dwType, dwSize
    As Offset hDevice
    As Unsigned Offset wParam
End Type

Type RAWMOUSE
    As Unsigned Integer usFlags
    $If 64BIT Then
        As String * 2 alignment
    $End If
    'As Unsigned Long ulButtons  'commented out because I'm creating this value using MAKELONG
    As Unsigned Integer usButtonFlags, usButtonData
    As Unsigned Long ulRawButtons
    As Long lLastX, lLastY
    As Unsigned Long ulExtraInformation
End Type

Type RAWINPUT
    As RAWINPUTHEADER header
    As RAWMOUSE mouse
End Type

' UDT TO HOLD THE INFO FOR EACH MOUSE
Type MouseInfoType
    UpdateCount As Integer ' if this value changes we know a value changed
    ID As String ' mouse device ID
    c As String ' cursor character
    x As Integer ' screen x position
    y As Integer ' screen y position
    dx As Integer ' mouse x movement -1=left, 1=right, 0=none
    dy As Integer ' mouse y movement -1=up  , 1=down , 0=none
    wheel As Integer ' mouse wheel value
    LeftDown As Integer ' tracks left mouse button state, TRUE=down
    MiddleDown As Integer ' tracks middle mouse button state, TRUE=down
    RightDown As Integer ' tracks right mouse button state, TRUE=down
    LeftCount As Integer ' counts left clicks
    MiddleCount As Integer ' counts middle clicks
    RightCount As Integer ' counts right clicks
End Type ' MouseInfoType

' UDT TO HOLD THE INFO FOR EACH KEYBOARD
Type KeyboardInfoType
    UpdateCount As Integer ' if this value changes we know a value changed
    ID As String ' keyboard device ID
    'TBD
End Type ' KeyboardInfoType

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' END UDTs
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' BEGIN API DECLARATIONS
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
' FOR RAW INPUT API
' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
Declare CustomType Library
    Function GetRawInputDeviceList~& (ByVal pRawInputDeviceList As Offset, Byval puiNumDevices As Offset, Byval cbSize As Unsigned Long)
    Sub GetRawInputDeviceList (ByVal pRawInputDeviceList As Offset, Byval puiNumDevices As Offset, Byval cbSize As Unsigned Long)
    Function RegisterRawInputDevices& (ByVal pRawInputDevices As Offset, Byval uiNumDevices As Unsigned Long, Byval cbSize As Unsigned Long)
    Function GetModuleHandle%& (ByVal lpModulename As Offset)
    Function LoadIcon%& (ByVal hInstance As Offset, Byval lpIconName As Offset)
    Function LoadCursor%& (ByVal hInstance As Offset, Byval lpCursorName As Offset)
    Function RegisterClassEx~% (ByVal wndclassex As Offset)
    Function CreateWindowEx%& (ByVal dwExStyle As Unsigned Long, Byval lpClassName As Offset, Byval lpWindowName As Offset, Byval dwStyle As Unsigned Long, Byval x As Long, Byval y As Long, Byval nWidth As Long, Byval nHeight As Long, Byval hWndParent As Offset, Byval hMenu As Offset, Byval hInstance As Offset, Byval lpParam As Offset)
    Sub ShowWindow (ByVal hWnd As Offset, Byval nCmdShow As Long)
    Sub UpdateWindow (ByVal hWnd As Offset)
    Function GetMessage& (ByVal lpMsg As Offset, Byval hWnd As Offset, Byval wMsgFilterMin As Unsigned Long, Byval wMsgFilterMax As Unsigned Long)
    Sub TranslateMessage (ByVal lpMsg As Offset)
    Sub DispatchMessage (ByVal lpMsg As Offset)
    Sub PostQuitMessage (ByVal nExitCode As Long)
    Function DefWindowProc%& (ByVal hWnd As Offset, Byval Msg As Unsigned Long, Byval wParam As Unsigned Offset, Byval lParam As Offset)
    Sub GetRawInputData (ByVal hRawInput As Offset, Byval uiCommand As Unsigned Long, Byval pData As Offset, Byval pcbSize As Offset, Byval cbSizeHeader As Unsigned Long)
    Function GetRawInputData~& (ByVal hRawInput As Offset, Byval uiCommand As Unsigned Long, Byval pData As Offset, Byval pcbSize As Offset, Byval cbSizeHeader As Unsigned Long)
    Sub InvalidateRect (ByVal hWnd As Offset, Byval lpRect As Offset, Byval bErase As Long)
    Sub SendMessage (ByVal hWnd As Offset, Byval Msg As Unsigned Long, Byval wParam As Unsigned Offset, Byval lParam As Offset)
    Function BeginPaint%& (ByVal hWnd As Offset, Byval lpPaint As Offset)
    Sub GetClientRect (ByVal hWnd As Offset, Byval lpRect As Offset)
    Sub DrawText (ByVal hdc As Offset, Byval lpchText As Offset, Byval cchText As Long, Byval lprc As Offset, Byval format As Unsigned Long)
    Sub OffsetRect (ByVal lprc As Offset, Byval dx As Long, Byval dy As Long)
    Sub EndPaint (ByVal hWnd As Offset, Byval lpPaint As Offset)
End Declare

' Header file "makeint.h" must be in same folder as this program.
Declare CustomType Library ".\makeint"
    Function MAKEINTRESOURCE%& Alias "MAKEINTRSC" (ByVal i As _Offset)
End Declare

Declare Library
    Function MAKELPARAM%& (ByVal l As Integer, Byval h As Integer)
    Function MAKELONG~& (ByVal l As Unsigned Integer, Byval h As Unsigned Integer)
End Declare

$If 64BIT Then
    Declare Library ".\internal\c\c_compiler\x86_64-w64-mingw32\include\windowsx"
    $Else
    Declare Library ".\internal\c\c_compiler\i686-w64-mingw32\include\windowsx"
    $End If
    Function GET_Y_LPARAM& (ByVal lp As Offset)
    Function GET_X_LPARAM& (ByVal lp As Offset)
End Declare

' Header file "winproc.h" must be in same folder as this program.
Declare Library ".\winproc"
    Function WindowProc%& ()
End Declare

' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
' FOR CONTROLLING WINDOW ON TOP, ETC.
' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
Declare Dynamic Library "user32"
    'Function FindWindowA%& (ByVal lpClassName%&, Byval lpWindowName%&)
    Function SetWindowPos& (ByVal hWnd%&, Byval hWndInsertAfter%&, Byval X&, Byval Y&, Byval cx&, Byval cy&, Byval uFlags~&)
    Function GetForegroundWindow%&
End Declare

Declare Dynamic Library "kernel32"
    Function GetLastError~& ()
End Declare

' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
' To keep focus on window
' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
'Declare Dynamic Library "user32"
'    Sub ShowWindow (ByVal hWnd As _Offset, Byval nCmdShow As Long)
'End Declare

' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
' To make window invisible
' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
Declare Dynamic Library "user32"
    Function SetLayeredWindowAttributes& (ByVal hwnd As _Offset, Byval crKey As Long, Byval bAlpha As _Unsigned _Byte, Byval dwFlags As Long)
    Function GetWindowLong& Alias "GetWindowLongA" (ByVal hwnd As _Offset, Byval nIndex As Long)
    Function SetWindowLong& Alias "SetWindowLongA" (ByVal hwnd As _Offset, Byval nIndex As Long, Byval dwNewLong As Long)
End Declare

' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
' Needed for acquiring the hWnd of the window
' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
Declare Library
    Function FindWindow& (ByVal ClassName As _Offset, WindowName$) ' To get hWnd handle
End Declare
' ----------------------------------------------------------------------------------------------------------------------------------------------------------------


' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' END API DECLARATIONS
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' BEGIN GLOBAL VARIABLES
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' BASIC PROGRAM METADATA
Dim Shared m_ProgramPath$: m_ProgramPath$ = Left$(Command$(0), _InStrRev(Command$(0), "\"))
Dim Shared m_ProgramName$: m_ProgramName$ = Mid$(Command$(0), _InStrRev(Command$(0), "\") + 1)
Dim Shared m_sTriggerFile As String: m_sTriggerFile = m_ProgramPath$ + "ReadMiceSub.DELETE-TO-CLOSE"

' GLOBAL VARIABLES TO TRACK ERROR STATE
Dim Shared m_sError As String: m_sError = ""
Dim Shared m_sIncludeError As String: m_sIncludeError = ""

' RAW INPUT VARIABLES
Dim Shared mousemessage As String
Dim Shared rawinputdevices As String

' MOUSE VARIABLES
Dim Shared arrMouse(0 To 8) As MouseInfoType ' STORES INFO FOR EACH MOUSE
'Dim Shared arrRawMouseID(8) As Long ' device IDs for mice connected to system (guessing this would be a string, dunno)
Dim Shared iMouseCount As Integer ' # OF MICE ATTACHED

' KEYBOARD VARIABLES
Dim Shared arrKeyboard(0 To 8) As KeyboardInfoType ' STORES INFO FOR EACH KEYBOARD
Dim Shared iKeyboardCount As Integer ' # OF KEYBOARDS ATTACHED
Dim Shared arrKeyState(0 To 8, 1 To 512) As Integer ' arrKeyState({device#}, {keyCode}) = TRUE if key {keyCode} on keyboard {device#} is currently held down.

Dim Shared arrScreen(1 To 80, 1 To 25) As String ' STORES TEXT FOR SCREEN

Dim Shared iMinX As Long
Dim Shared iMaxX As Long
Dim Shared iMinY As Long
Dim Shared iMaxY As Long

' RAW FILE NAMES
Dim Shared arrFile(0 To 31, 0 To 1) As String

' NETWORK VARIABLES
Dim Shared uintPort As _Unsigned Integer ' port
Dim Shared lngConn As Long ' c&
Dim Shared iData As Integer ' i
Dim Shared sOutput As String ' s$

' HANDLE FOR THE PROGRAM WINDOW
Dim Shared MyHwnd As _Offset ' _Integer64 hwnd%&
'Dim As Offset hwndMain
Dim Shared hwndMain As _Offset

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' END GLOBAL VARIABLES
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' EXECUTION STARTS HERE!
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' =============================================================================
' START THE MAIN ROUTINE
main

' =============================================================================
' FINISH
'Print m_ProgramName$ + " finished."
'End
System ' return control to the operating system

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' BEGIN GLOBAL ERROR HANDLER
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ErrorHandler:
m_sError = "Error #" + _Trim$(Str$(Err)) + " at line " + _Trim$(Str$(_ErrorLine)) + "."
m_sIncludeError = "File " + Chr$(34) + _InclErrorFile$ + Chr$(34) + " at line " + _Trim$(Str$(_InclErrorLine)) + "."
Resume Next
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' END GLOBAL ERROR HANDLER
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' BEGIN DATA STATEMENTS
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' MOUSE CURSORS (JUST SOME LETTERS)
CData:
Data A,b,C,D,E,f,G,H

' DEFAULT/INTIAL X COORDINATE OF EACH CURSOR ON SCREEN
XData:
Data 5,15,25,35,45,55,65,75

' DEFAULT/INTIAL Y COORDINATE OF EACH CURSOR ON SCREEN
YData:
Data 17,17,19,19,21,21,23,23

' DEFAULT/INITIAL VALUE OF EACH SCROLL WHEEL
WData:
Data 224,192,160,128,96,64,32,0
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' END DATA STATEMENTS
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' BEGIN MAIN ROUTINES
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

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

Sub main
    Dim sPort As String
    Dim iLoop As Integer
    Dim in$

    ' MAKE SURE WE HAVE INPUT
    sPort = Command$(1)
    If Len(sPort) > 0 Then
        If IsNumber%(sPort) = TRUE Then
            ' OPEN CONNECTION
            uintPort = Val(sPort)
            lngConn = _OpenClient("tcp/ip:" + _Trim$(Str$(uintPort)) + ":localhost")
            Print lngConn



            ' ****************************************************************************************************************************************************************
            ' ****************************************************************************************************************************************************************
            ' RETHINK DATA STRUCTURE
            ' ****************************************************************************************************************************************************************
            ' ****************************************************************************************************************************************************************
            ' INITIALIZE
            For iLoop = LBound(arrFile) To UBound(arrFile)
                arrFile(iLoop, cFileName) = m_ProgramPath$ + "mouse" + _Trim$(Str$(iLoop)) + ".txt"
                arrFile(iLoop, cFileData) = ""
            Next iLoop

            ' INITIALIZE
            iMinX = 0
            iMaxX = 3583
            iMinY = 0
            iMaxY = 8202

            ' ****************************************************************************************************************************************************************
            ' ****************************************************************************************************************************************************************
            ' SET UP WINDOW TO BE SAME SIZE AS, AND OVERLAPPED WITH HOST WINDOW
            ' ****************************************************************************************************************************************************************
            ' ****************************************************************************************************************************************************************
            ' SET UP WINDOW
            'Screen _NewImage(1024, 768, 32)
            Screen 12 ' SCREEN 12 can use 16 color attributes with a black background. 256K possible RGB color hues. Background colors can be used with QB64.

            ' window needs to be lined up directly under the main program, so the mouse coordinates align with the display
            _ScreenMove 0, 0 ' <<< NOT WORKING, HOW DO WE DO THIS IN THE EVENT MODEL?



            ' CREATE TRIGGER FILE
            Open m_sTriggerFile For Output As #1
            Print #1, "Deleting this file will cause program " + m_ProgramName$ + " to stop running."
            Close #1

            ' GET HANDLE TO THE PROGRAM WINDOW
            Do
                MyHwnd = _WindowHandle
            Loop Until MyHwnd




            ' GIVE CONTROL TO THE EVENT-ORIENTED CODE
            System Val(Str$(WinMain))



        Else
            Print "Invalid non-numeric input " + Chr$(34) + sPort + Chr$(34) + ". Exiting."
        End If
    Else
        Print "No input. Exiting."
    End If

End Sub ' main

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' END MAIN ROUTINES
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' BEGIN RAW INPUT FUNCTIONS
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' /////////////////////////////////////////////////////////////////////////////
' Runs first

Function WinMain~%& ()
    'Dim As Offset hwndMain
    Dim As Offset hInst
    Dim As Offset hWndTop

    Dim As MSG msg
    Dim As WNDCLASSEX wndclass
    Dim As String szMainWndClass
    Dim As String szWinTitle
    Dim As Unsigned Integer reg
    Dim sData As String

    'DEBUG: TRY FULL SCREEN <- PROGRAM CRASHES!
    '_FullScreen _SquarePixels

    hInst = GetModuleHandle(0)
    szMainWndClass = "WinTestWin" + Chr$(0)
    'szWinTitle = "Hello" + Chr$(0)
    szWinTitle = cProgName + Chr$(0)

    wndclass.lpszClassName = Offset(szMainWndClass)
    wndclass.cbSize = Len(wndclass)
    wndclass.style = CS_HREDRAW Or CS_VREDRAW
    wndclass.lpfnWndProc = WindowProc
    wndclass.hInstance = hInst 'GetModuleHandle(0) will return the hInstance of this EXE
    wndclass.hIcon = LoadIcon(0, MAKEINTRESOURCE(IDI_APPLICATION))
    wndclass.hIconSm = LoadIcon(0, MAKEINTRESOURCE(IDI_APPLICATION))
    wndclass.hCursor = LoadCursor(0, MAKEINTRESOURCE(IDC_ARROW))
    wndclass.hbrBackground = COLOR_WINDOW + 1

    reg = RegisterClassEx(Offset(wndclass)) 'I prefer to use the output of RegisterClassEx rather than the window name

    'DEBUG: SUBSTITUTE _WindowHandle

    'Function  CreateWindowEx%& (
    '   ByVal dwExStyle    As Unsigned Long = 0
    '   Byval lpClassName  As Offset        = MAKELPARAM(reg, 0)
    '   Byval lpWindowName As Offset        = Offset(szWinTitle)
    '   Byval dwStyle      As Unsigned Long = WS_OVERLAPPEDWINDOW
    '   Byval x            As Long          = CW_USEDEFAULT
    '   Byval y            As Long          = CW_USEDEFAULT
    '   Byval nWidth       As Long          = CW_USEDEFAULT
    '   Byval nHeight      As Long          = CW_USEDEFAULT
    '   Byval hWndParent   As Offset        = 0
    '   Byval hMenu        As Offset        = 0
    '   Byval hInstance    As Offset        = hInst
    '   Byval lpParam      As Offset        = 0

    '    hwndMain = CreateWindowEx( _
    '       0, _
    '       MAKELPARAM(reg, 0), _
    '       Offset(szWinTitle), _
    '       WS_OVERLAPPEDWINDOW, _
    '       CW_USEDEFAULT, _
    '       CW_USEDEFAULT, _
    '       CW_USEDEFAULT, _
    '       CW_USEDEFAULT, _
    '       0, _
    '       0, _
    '       hInst, _
    '       0)

    hwndMain = CreateWindowEx( _
        0, _
        MAKELPARAM(reg, 0), _
        Offset(szWinTitle), _
        WS_OVERLAPPEDWINDOW, _
        0, _
        0, _
        1024, _
        768, _
        0, _
        0, _
        hInst, _
        0)

    'hwndMain = _WindowHandle
    ShowWindow hwndMain, SW_SHOW


    ' TURN SUB WINDOW INVISIBLE
    ''SetWindowOpacity MyHwnd, cInvisible
    'SetWindowOpacity hwndMain, cInvisible
    SetWindowOpacity hwndMain, cTransparent

    ' KEEP WINDOW VISIBLE
    UpdateWindow hwndMain
    'DEBUG: SUBSTITUTE _WindowHandle
    'UpdateWindow _WindowHandle


    '' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
    '' SEND SUB WINDOW HANDLE BACK TO MAIN
    'sData = _Trim$(Str$(hwndMain))
    'Put #lngConn, , sData
    '' ----------------------------------------------------------------------------------------------------------------------------------------------------------------


    ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
    ' MOVE WINDOW TO TOP
    ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
    '' GET WINDOW HANDLES
    'hWndThis = _WindowHandle ' FindWindowA(0, _OFFSET(t))
    hWndTop = GetForegroundWindow%& ' find currently focused process handle

    ' GET FOCUS
    If hwndMain <> hWndTop Then
        _ScreenClick 240, 240 ' add 40 to x and y to focus on positioned window
    End If

    ' MOVE TO TOP
    If SetWindowPos(hwndMain, HWND_TOPMOST, 200, 200, 0, 0, SWP_NOSIZE Or SWP_NOACTIVATE) = 0 Then
        'sNextError = "SetWindowPos failed. 0x" + LCase$(Hex$(GetLastError))
        m_sError = "SetWindowPos failed. 0x" + LCase$(Hex$(GetLastError))
    End If

    ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
    ' START THE INPUT ROUTINES
    ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
    InitRawInput
    InitMouseTest 'TODO: SAVE_MOUSE_INFO

    ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
    ' MAIN LOOP
    ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
    While GetMessage(Offset(msg), 0, 0, 0)
        TranslateMessage Offset(msg)
        DispatchMessage Offset(msg)


        ' QUIT IF TRIGGER FILE IS GONE
        If _FileExists(m_sTriggerFile) = FALSE Then
            System
        End If



        ' SEE IF WE CAN DETECT KEYPRESSES
        ' IF USER PRESSES A THEN SHOW WINDOW
        If _KeyDown(65) Or _KeyDown(97) Then
            Beep
            'SetWindowOpacity MyHwnd, cVisible
        End If

        ' IF USER PRESSES B THEN MAKE WINDOW TRANSPARENT
        If _KeyDown(66) Or _KeyDown(98) Then
            Beep
            Beep
            'SetWindowOpacity MyHwnd, cTransparent
        End If

        ' IF USER PRESSES C THEN HIDE WINDOW
        If _KeyDown(67) Or _KeyDown(99) Then
            Beep
            Beep
            Beep
            'SetWindowOpacity MyHwnd, cInvisible
        End If

        ' IF USER PRESSES ESCAPE THEN EXIT
        If _KeyDown(27) Then
            DeleteFile m_sTriggerFile
            'System
        End If

        ' KEEP WINDOW ON TOP
        If _WindowHasFocus = 0 Then
            _ScreenIcon
            ''ShowWindow MyHwnd, 1
            'ShowWindow hwndMain, 1
            ShowWindow hwndMain, SW_SHOW
        End If




    Wend




    WinMain = msg.wParam
End Function ' WinMain

' /////////////////////////////////////////////////////////////////////////////
' Handles main window events

Function MainWndProc%& (hwnd As Offset, nMsg As Unsigned Long, wParam As Unsigned Offset, lParam As Offset)
    Static As Offset hwndButton
    Static As Long cx, cy
    Dim As Offset hdc
    Dim As PAINTSTRUCT ps
    Dim As RECT rc
    Dim As MEM lpb
    Dim As Unsigned Long dwSize
    Dim As RAWINPUT raw
    Dim As Long tmpx, tmpy
    Static As Long maxx
    Dim As RAWINPUTHEADER rih

    ' TEMP VARIABLES FOR DISPLAYING FORMATTED VALUES TO SCREEN
    Dim strNextID As String
    Dim iIndex As Integer
    Dim iRowOffset As Integer
    Dim iLen As Integer
    Dim sCount As String
    Dim sX As String
    Dim sY As String
    Dim sWheel As String
    Dim sLeftDown As String
    Dim sMiddleDown As String
    Dim sRightDown As String
    Dim sLeftCount As String
    Dim sMiddleCount As String
    Dim sRightCount As String
    Dim sNext As String
    Dim iNewX As Integer
    Dim iNewY As Integer
    Dim iDX As Integer
    Dim iDY As Integer

    ' MORE TEMP VARIABLES
    Dim iMouseNum As Integer

    ' HANDLE EVENTS
    Select Case nMsg
        Case WM_DESTROY
            PostQuitMessage 0
            MainWndProc = 0
            Exit Function

        Case WM_INPUT
            GetRawInputData lParam, RID_INPUT, 0, Offset(dwSize), Len(rih)
            lpb = MemNew(dwSize)
            If lpb.SIZE = 0 Then
                MainWndProc = 0
                Exit Function
            End If
            If GetRawInputData(lParam, RID_INPUT, lpb.OFFSET, Offset(dwSize), Len(rih)) <> dwSize Then
                'Print "GetRawInputData doesn't return correct size!"
                mousemessage = "GetRawInputData doesn't return correct size!"
            End If
            MemGet lpb, lpb.OFFSET, raw

            If raw.header.dwType = RIM_TYPEMOUSE Then
                tmpx = raw.mouse.lLastX
                tmpy = raw.mouse.lLastY
                maxx = tmpx

                ' GET MOUSE INFO
                ' NOTES:
                ' ulButtons and usButtonFlags both return the same thing (buttons)
                ' usButtonData changes value when scroll wheel moved (just stays at one value)
                'mousemessage = ""
                'mousemessage = mousemessage + "Mouse:hDevice" + Str$(raw.header.hDevice)
                'mousemessage = mousemessage + "usFlags=" + Hex$(raw.mouse.usFlags)
                'mousemessage = mousemessage + "ulButtons=" + Hex$(MAKELONG(raw.mouse.usButtonFlags, raw.mouse.usFlags))
                'mousemessage = mousemessage + "usButtonFlags=" + Hex$(raw.mouse.usButtonFlags)
                'mousemessage = mousemessage + "usButtonData=" + Hex$(raw.mouse.usButtonData)
                'mousemessage = mousemessage + "ulRawButtons=" + Hex$(raw.mouse.ulRawButtons)
                'mousemessage = mousemessage + "lLastX=" + Str$(raw.mouse.lLastX)
                'mousemessage = mousemessage + "lLastY=" + Str$(raw.mouse.lLastY)
                'mousemessage = mousemessage + "ulExtraInformation=" + Hex$(raw.mouse.ulExtraInformation) + Chr$(13)

                ' UPDATE RANGE OF MOUSE COORDINATES
                If GET_X_LPARAM(lParam) < iMinX Then iMinX = GET_X_LPARAM(lParam)
                If GET_X_LPARAM(lParam) > iMaxX Then iMaxX = GET_X_LPARAM(lParam)
                If GET_Y_LPARAM(lParam) < iMinY Then iMinY = GET_Y_LPARAM(lParam)
                If GET_Y_LPARAM(lParam) > iMaxY Then iMaxY = GET_Y_LPARAM(lParam)

                ' IDENTIFY WHICH MOUSE IT IS
                strNextID = _Trim$(Str$(raw.header.hDevice))
                iIndex = GetMouseIndex%(strNextID)
                If iIndex >= LBound(arrMouse) Then
                    If iIndex <= UBound(arrMouse) Then

                        ' =============================================================================
                        ' READ MOUSE MOVEMENT

                        ' DOESN'T WORK, MOVES ALL OVER THE PLACE:
                        '' METHOD #1: SCALE MOUSE POSITION TO 80X25 POSITION
                        'iNewX = ( (GET_X_LPARAM(lParam) + 1) * 80) \ (iMaxX+1)
                        'iNewY = ( (GET_Y_LPARAM(lParam) + 1) * 25) \ (iMaxY+1)
                        'arrMouse(iIndex).x = iNewX
                        'arrMouse(iIndex).y = iNewY

                        ' WORKS BUT NOT THAT ACCURATE:
                        ' METHOD #2: INCREMENT/DECREMENT DELTA
                        If raw.mouse.lLastX < 0 Then
                            arrMouse(iIndex).dx = -1
                        ElseIf raw.mouse.lLastX > 0 Then
                            arrMouse(iIndex).dx = 1
                        Else
                            arrMouse(iIndex).dx = 0
                        End If

                        If raw.mouse.lLastY < 0 Then
                            arrMouse(iIndex).dy = -1
                        ElseIf raw.mouse.lLastY > 0 Then
                            arrMouse(iIndex).dy = 1
                        Else
                            arrMouse(iIndex).dy = 0
                        End If

                        ' =============================================================================
                        'TODO: SAVE SCROLL WHEEL + BUTTONS
                        'Hex$(raw.mouse.usButtonFlags)

                        ' left button = 1 when down, 2 when released
                        If ((raw.mouse.usButtonFlags And 1) = 1) Then
                            arrMouse(iIndex).LeftDown = TRUE
                        ElseIf ((raw.mouse.usButtonFlags And 2) = 2) Then
                            arrMouse(iIndex).LeftDown = FALSE
                        End If

                        ' middle button = 16 when down, 32 when released
                        If ((raw.mouse.usButtonFlags And 16) = 16) Then
                            arrMouse(iIndex).MiddleDown = TRUE
                        ElseIf ((raw.mouse.usButtonFlags And 32) = 32) Then
                            arrMouse(iIndex).MiddleDown = FALSE
                        End If

                        ' right button = 4 when down, 8 when released
                        If ((raw.mouse.usButtonFlags And 4) = 4) Then
                            arrMouse(iIndex).RightDown = TRUE
                        ElseIf ((raw.mouse.usButtonFlags And 8) = 8) Then
                            arrMouse(iIndex).RightDown = FALSE
                        End If

                        ' scroll wheel = ???
                        'arrMouse(iIndex).wheel = ???

                        ' DID VALUE CHANGE?
                        If arrMouse(iIndex).UpdateCount = 32767 Then
                            arrMouse(iIndex).UpdateCount = 1
                        Else
                            arrMouse(iIndex).UpdateCount = arrMouse(iIndex).UpdateCount + 1
                        End If

                        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
                        ' COLLECT VALUES FOR THIS MOUSE TO SEND
                        ' IN THE FOLOWING TAB-DELIMITED FORMAT:
                        ' {mouse #}\t{count}\t{dx}\t{dy}\t{wheel}\t{leftDown}\t{middleDown}\t{rightDown}\n
                        sOutput = ""
                        sOutput = sOutput + _Trim$(Str$(iIndex)) + Chr$(9)
                        sOutput = sOutput + _Trim$(Str$(arrMouse(iIndex).UpdateCount)) + Chr$(9)
                        sOutput = sOutput + _Trim$(Str$(arrMouse(iIndex).dx)) + Chr$(9)
                        sOutput = sOutput + _Trim$(Str$(arrMouse(iIndex).dy)) + Chr$(9)
                        sOutput = sOutput + _Trim$(Str$(arrMouse(iIndex).wheel)) + Chr$(9)
                        sOutput = sOutput + _Trim$(Str$(arrMouse(iIndex).LeftDown)) + Chr$(9)
                        sOutput = sOutput + _Trim$(Str$(arrMouse(iIndex).MiddleDown)) + Chr$(9)
                        sOutput = sOutput + _Trim$(Str$(arrMouse(iIndex).RightDown)) + Chr$(13)

                        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
                        ' SEND VALUES FOR THIS MOUSE TO HOST
                        Put #lngConn, , sOutput

                        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
                        ' CLEAR MOVEMENT
                        arrMouse(iIndex).dx = 0
                        arrMouse(iIndex).dy = 0

                    End If
                End If

                ' UPDATE mousemessage WITH PLAYING FIELD
                mousemessage = ScreenToString$
                ' ================================================================================================================================================================
                ' END WRITE OUTPUT FILE
                ' ================================================================================================================================================================

                InvalidateRect hwnd, 0, -1
                SendMessage hwnd, WM_PAINT, 0, 0
                MainWndProc = 0

            ElseIf raw.header.dwType = RIM_TYPEKEYBOARD Then
                ' TODO: READ KEYBOARD INPUT

                ' SEE:
                ' https://www.codeproject.com/Articles/17123/Using-Raw-Input-from-C-to-handle-multiple-keyboard
                ' https://forum.powerbasic.com/forum/user-to-user-discussions/powerbasic-for-windows/55985-raw-keyboard-hid-input-discussion
                ' https://hackaday.io/project/5364-cheap-windows-jogkeyboard-controller-for-cncs/log/16843-looking-at-rawinput-for-more-detail

                ' WinAPI Raw Input confusion - For Beginners - GameDev.net
                ' https://www.gamedev.net/forums/topic/700010-winapi-raw-input-confusion/

                'iKeyboardCount = iKeyboardCount + 1 ' # KEYBOARDS ATTACHED
                'strNextID = _Trim$(Str$(rawdevs(x).hDevice))
                'arrKeyboard(iKeyboardCount - 1).ID = strNextID
                ' TODO: READ KEYBOARD AND STORE KEYBOARD STATE
                'arrKeyState(0 To 8, 1 To 512) As Integer ' arrKeyState({device#}, {keyCode}) = TRUE if key {keyCode} on keyboard {device#} is currently held down.

            End If

            MemFree lpb
            MainWndProc = 0
            Exit Function

        Case WM_MOUSEMOVE
            'mousemessage = mousemessage + " X:" + Str$(GET_X_LPARAM(lParam))
            'mousemessage = mousemessage + " Y:" + Str$(GET_Y_LPARAM(lParam))
            'mousemessage = mousemessage + Chr$(0)

            ' SAVE RANGE OF MOUSE COORDINATES
            If GET_X_LPARAM(lParam) < iMinX Then
                iMinX = GET_X_LPARAM(lParam)
                arrMouse(iIndex).dx = -1
            ElseIf GET_X_LPARAM(lParam) > iMaxX Then
                iMaxX = GET_X_LPARAM(lParam)
                arrMouse(iIndex).dx = 1
            Else
                arrMouse(iIndex).dx = 0
            End If

            If GET_Y_LPARAM(lParam) < iMinY Then
                iMinY = GET_Y_LPARAM(lParam)
                arrMouse(iIndex).dy = -1
            ElseIf GET_Y_LPARAM(lParam) > iMaxY Then
                iMaxY = GET_Y_LPARAM(lParam)
                arrMouse(iIndex).dy = 1
            Else
                arrMouse(iIndex).dy = 0
            End If

            ' IDENTIFY WHICH MOUSE IT IS
            strNextID = _Trim$(Str$(raw.header.hDevice))
            iIndex = GetMouseIndex%(strNextID)
            If iIndex >= LBound(arrMouse) Then
                If iIndex <= UBound(arrMouse) Then

                    ' =============================================================================
                    ' UPDATE ABSOLUTE POSITION

                    ' DOESN'T WORK, MOVES ALL OVER THE PLACE:
                    '' METHOD #1: SCALE MOUSE POSITION TO 80X25 POSITION
                    ''iNewX = ( (GET_X_LPARAM(lParam) + 1) * 80) \ 1520
                    'iNewX = ( (GET_X_LPARAM(lParam) + 1) * 80) \ (iMaxX+1)
                    ''iNewY = ( (GET_Y_LPARAM(lParam) + 1) * 25) \ 782
                    'iNewY = ( (GET_Y_LPARAM(lParam) + 1) * 25) \ (iMaxY+1)
                    'arrMouse(iIndex).x = iNewX
                    'arrMouse(iIndex).y = iNewY

                    ' WORKS BUT NOT THAT ACCURATE:
                    ' METHOD #2: INCREMENT/DECREMENT DELTA
                    ' (should we update here too?)

                    'TODO: SAVE SCROLL WHEEL + BUTTONS
                    ' (should we update here too?)
                    'arrMouse(iIndex).wheel =
                    'arrMouse(iIndex).LeftDown =
                    'arrMouse(iIndex).MiddleDown =
                    'arrMouse(iIndex).RightDown =
                End If
            End If

            'DEBUG: SUBSTITUTE _WindowHandle
            InvalidateRect hwnd, 0, -1
            'InvalidateRect _WindowHandle, 0, -1

            'DEBUG: SUBSTITUTE _WindowHandle
            SendMessage hwnd, WM_PAINT, 0, 0
            'SendMessage _WindowHandle, WM_PAINT, 0, 0

            MainWndProc = 0
            Exit Function

        Case WM_PAINT
            'DEBUG: SUBSTITUTE _WindowHandle
            hdc = BeginPaint(hwnd, Offset(ps))
            'hdc = BeginPaint(_WindowHandle, Offset(ps))

            'DEBUG: SUBSTITUTE _WindowHandle
            GetClientRect hwnd, Offset(rc)
            'GetClientRect _WindowHandle, Offset(rc)

            DrawText hdc, Offset(mousemessage), Len(mousemessage), Offset(rc), DT_CENTER
            OffsetRect Offset(rc), 0, 200

            '' PRINT LIST OF RawInput DEVICES:
            'DrawText hdc, Offset(rawinputdevices), Len(rawinputdevices), Offset(rc), DT_CENTER

            'DEBUG: SUBSTITUTE _WindowHandle
            EndPaint hwnd, Offset(ps)
            'EndPaint _WindowHandle, Offset(ps)

            MainWndProc = 0
            Exit Function

        Case Else
            'DEBUG: SUBSTITUTE _WindowHandle
            MainWndProc = DefWindowProc(hwnd, nMsg, wParam, lParam)
            'MainWndProc = DefWindowProc(_WindowHandle, nMsg, wParam, lParam)
    End Select

    If _KeyDown(27) Then End

End Function ' MainWndProc

' /////////////////////////////////////////////////////////////////////////////
' Initializes raw input stuff

Sub InitRawInput ()
    Dim As RAWINPUTDEVICE Rid(0 To 49)
    Dim As Unsigned Long nDevices
    Dim As RAWINPUTDEVICELIST RawInputDeviceList
    Dim As MEM pRawInputDeviceList
    ReDim As RAWINPUTDEVICELIST rawdevs(-1)
    Dim As Unsigned Long x
    Dim strNextID As String
    'dim lngNextID as long

    If GetRawInputDeviceList(0, Offset(nDevices), Len(RawInputDeviceList)) <> 0 Then
        Exit Sub
    End If

    pRawInputDeviceList = MemNew(Len(RawInputDeviceList) * nDevices)
    GetRawInputDeviceList pRawInputDeviceList.OFFSET, Offset(nDevices), Len(RawInputDeviceList)

    ' This small block of commented code proves that we've got the device list
    ReDim As RAWINPUTDEVICELIST rawdevs(0 To nDevices - 1)
    MemGet pRawInputDeviceList, pRawInputDeviceList.OFFSET, rawdevs()

    ' GET MOUSE / KEYBOARD INFO
    iMouseCount = 0
    iKeyboardCount = 0

    rawinputdevices = "Number of raw input devices:" + Str$(nDevices) + Chr$(13)

    For x = 0 To UBound(rawdevs)
        rawinputdevices = rawinputdevices + Str$(rawdevs(x).hDevice) + ":" + Str$(rawdevs(x).dwType) + Chr$(13)

        ' RAWINPUTHEADER (winuser.h) - Win32 apps | Microsoft Learn
        ' https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-rawinputheader
        ' dwType
        ' Type: DWORD
        ' The type of raw input. It can be one of the following values:
        ' Constant           Value   Meaning
        ' RIM_TYPEMOUSE      0       Raw input comes from the mouse.
        ' RIM_TYPEKEYBOARD   1       Raw input comes from the keyboard.
        ' RIM_TYPEHID        2       Raw input comes from some device that is not a keyboard or a mouse.

        ' WHAT TYPE OF DEVICE IS IT?
        'If rawdevs(x).dwType = 0 Then
        If rawdevs(x).dwType = RIM_TYPEMOUSE Then
            iMouseCount = iMouseCount + 1
            strNextID = _Trim$(Str$(rawdevs(x).hDevice))
            'lngNextID = Val(strNextID)
            'arrMouse(iMouseCount-1).ID = lngNextID
            arrMouse(iMouseCount - 1).ID = strNextID
            arrMouse(iMouseCount - 1).UpdateCount = 0
            'TODO: SAVE_MOUSE_INFO

        ElseIf rawdevs(x).dwType = RIM_TYPEKEYBOARD Then
            iKeyboardCount = iKeyboardCount + 1 ' # KEYBOARDS ATTACHED
            strNextID = _Trim$(Str$(rawdevs(x).hDevice))
            arrKeyboard(iKeyboardCount - 1).ID = strNextID
            arrKeyboard(iKeyboardCount - 1).UpdateCount = 0
            ' TODO: READ KEYBOARD AND STORE KEYBOARD STATE

            'arrKeyState(0 To 8, 1 To 512) As Integer ' arrKeyState({device#}, {keyCode}) = TRUE if key {keyCode} on keyboard {device#} is currently held down.
        End If

    Next x
    rawinputdevices = rawinputdevices + Chr$(0)

    MemFree pRawInputDeviceList

    Rid(0).usUsagePage = &H01
    Rid(0).usUsage = &H02
    Rid(0).dwFlags = 0

    'DEBUG: SUBSTITUTE _WindowHandle
    Rid(0).hwndTarget = 0
    'Rid(0).hwndTarget = _WindowHandle

    If RegisterRawInputDevices(Offset(Rid()), 1, Len(Rid(0))) = 0 Then
        mousemessage = "RawInput init failed" + Chr$(0)
    End If
End Sub ' InitRawInput

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' END RAW INPUT FUNCTIONS
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' BEGIN WINDOW FUNCTIONS
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' /////////////////////////////////////////////////////////////////////////////
' hWnd = handle to window to set opacity for
' Level = 0 TO 255, 0=totally invisible, 128=transparent, 255=100% solid

Sub SetWindowOpacity (hWnd As _Offset, Level As _Unsigned _Byte)
    Const cIndex = -20
    Const LWA_ALPHA = &H2
    Const WS_EX_LAYERED = &H80000

    Dim lngMsg As Long
    Dim lngValue As Long

    'Function GetWindowLong& Alias "GetWindowLongA" (ByVal hwnd As _Offset, Byval nIndex As Long)
    lngMsg = GetWindowLong(hWnd, cIndex)
    lngMsg = lngMsg Or WS_EX_LAYERED

    'Function SetWindowLong& Alias "SetWindowLongA" (ByVal hwnd As _Offset, Byval nIndex As Long, Byval dwNewLong As Long)
    lngValue = SetWindowLong(hWnd, cIndex, lngMsg)

    'Function SetLayeredWindowAttributes& (ByVal hwnd As _Offset, Byval crKey As Long, Byval bAlpha As _Unsigned _Byte, Byval dwFlags As Long)
    lngValue = SetLayeredWindowAttributes(hWnd, 0, Level, LWA_ALPHA)
End Sub ' SetWindowOpacity

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' END WINDOW FUNCTIONS
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' BEGIN MOUSE TEST FUNCTIONS #1
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' /////////////////////////////////////////////////////////////////////////////
' Initialize mouse test stuff

'TODO: SAVE_MOUSE_INFO

Sub InitMouseTest
    Dim iIndex As Integer
    Dim iLoop As Integer

    ' FOR NOW ONLY SUPPORT UPTO 8 MICE
    If (iMouseCount > 8) Then iMouseCount = 8

    ' INITIALIZE CURSORS, MOUSE STATE, ETC.
    Restore CData
    iIndex = LBound(arrMouse) - 1
    For iLoop = 1 To iMouseCount
        iIndex = iIndex + 1
        Read arrMouse(iIndex).c
        ' INITIALIZED BELOW: arrMouse(iIndex).x = 0
        ' INITIALIZED BELOW: arrMouse(iIndex).y = 0
        ' INITIALIZED BELOW: arrMouse(iIndex).wheel = 127
        arrMouse(iIndex).LeftDown = FALSE
        arrMouse(iIndex).MiddleDown = FALSE
        arrMouse(iIndex).RightDown = FALSE
        arrMouse(iIndex).LeftCount = 0
        arrMouse(iIndex).MiddleCount = 0
        arrMouse(iIndex).RightCount = 0
        arrMouse(iIndex).UpdateCount = 1
    Next iLoop

    ' INITIALIZE X COORDINATES
    Restore XData
    iIndex = LBound(arrMouse) - 1
    For iLoop = 1 To iMouseCount
        iIndex = iIndex + 1
        Read arrMouse(iIndex).x
    Next iLoop

    ' INITIALIZE Y COORDINATES
    Restore YData
    iIndex = LBound(arrMouse) - 1
    For iLoop = 1 To iMouseCount
        iIndex = iIndex + 1
        Read arrMouse(iIndex).y
    Next iLoop

    ' INITIALIZE SCROLL WHEEL
    Restore WData
    iIndex = LBound(arrMouse) - 1
    For iLoop = 1 To iMouseCount
        iIndex = iIndex + 1
        Read arrMouse(iIndex).wheel
    Next iLoop

End Sub ' InitMouseTest

' /////////////////////////////////////////////////////////////////////////////
' Finds position in array arrMouse where .ID = MouseID

Function GetMouseIndex% (MouseID As String)
    Dim iLoop As Integer
    Dim iIndex%
    iIndex% = LBound(arrMouse) - 1
    For iLoop = LBound(arrMouse) To UBound(arrMouse)
        If arrMouse(iLoop).ID = MouseID Then
            iIndex% = iLoop
            Exit For
        Else
            ' not it
        End If
    Next iLoop
    GetMouseIndex% = iIndex%
End Function ' GetMouseIndex%

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' END MOUSE TEST FUNCTIONS #1
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' BEGIN TEST OUTPUT FUNCTIONS FOR API CONTROLLED UI
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' /////////////////////////////////////////////////////////////////////////////
' Clears global array arrScreen

Sub ClearText
    Dim iColNum As Integer
    Dim iRowNum As Integer
    For iColNum = LBound(arrScreen, 1) To UBound(arrScreen, 1)
        For iRowNum = LBound(arrScreen, 2) To UBound(arrScreen, 2)
            arrScreen(iColNum, iRowNum) = " "
        Next iRowNum
    Next iColNum
End Sub ' ClearText

' /////////////////////////////////////////////////////////////////////////////
' Plots string MyString to position (iX, iY) in global array arrScreen.

Sub WriteText (iRow As Integer, iColumn As Integer, MyString As String)
    Dim iPos As Integer
    Dim iLoop As Integer
    If iColumn > 0 And iColumn < 81 Then
        If iRow > 0 And iRow < 26 Then
            For iLoop = 1 To Len(MyString)
                iPos = iColumn + (iLoop - 1)
                If iPos < 81 Then
                    arrScreen(iPos, iRow) = Mid$(MyString, iLoop, 1)
                Else
                    Exit For
                End If
            Next iLoop
        End If
    End If
End Sub ' WriteText

' /////////////////////////////////////////////////////////////////////////////
' Converts global array arrScreen to a string.

Function ScreenToString$
    Dim sResult As String
    Dim iColNum As Integer
    Dim iRowNum As Integer
    sResult = ""
    For iRowNum = LBound(arrScreen, 2) To UBound(arrScreen, 2)
        For iColNum = LBound(arrScreen, 1) To UBound(arrScreen, 1)
            sResult = sResult + arrScreen(iColNum, iRowNum)
        Next iColNum
        sResult = sResult + Chr$(13)
    Next iRowNum
    ScreenToString$ = sResult
End Function ' ScreenToString$

' /////////////////////////////////////////////////////////////////////////////
' based on code from:
' Qbasic Programs - Download free bas source code
' http://www.thedubber.altervista.org/qbsrc.htm

Sub DrawTextLine (y%, x%, y2%, x2%, c$)
    Dim i%
    Dim steep%
    Dim e%
    Dim sx%
    Dim dx%
    Dim sy%
    Dim dy%

    i% = 0: steep% = 0: e% = 0
    If (x2% - x%) > 0 Then sx% = 1: Else sx% = -1
    dx% = Abs(x2% - x%)
    If (y2% - y%) > 0 Then sy% = 1: Else sy% = -1
    dy% = Abs(y2% - y%)
    If (dy% > dx%) Then
        steep% = 1
        Swap x%, y%
        Swap dx%, dy%
        Swap sx%, sy%
    End If
    e% = 2 * dy% - dx%
    For i% = 0 To dx% - 1
        If steep% = 1 Then
            ''PSET (y%, x%), c%:
            'Locate y%, x% : Print c$;
            WriteText y%, x%, c$
        Else
            ''PSET (x%, y%), c%
            'Locate x%, y% : Print c$;
            WriteText x%, y%, c$
        End If

        While e% >= 0
            y% = y% + sy%: e% = e% - 2 * dx%
        Wend
        x% = x% + sx%: e% = e% + 2 * dy%
    Next
    ''PSET (x2%, y2%), c%
    'Locate x2%, y2% : Print c$;
    WriteText x2%, y2%, c$
End Sub ' DrawTextLine

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' END TEST OUTPUT FUNCTIONS FOR API CONTROLLED UI
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' BEGIN MOUSE FUNCTIONS TO COME
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' /////////////////////////////////////////////////////////////////////////////
' Returns a count of # of RawInput mouse devices connected to the system

' *****************************************************************************
' TODO: GET COUNT FROM RawInput API
' For now, hardcoded to 1 until we figure out how to do this.
' *****************************************************************************
Function GetRawMouseCount% ()
    GetRawMouseCount% = 1
End Function ' GetRawMouseCount%

' /////////////////////////////////////////////////////////////////////////////
' Gets ID of each RawInput mouse device connected to the system (for now upto 8)

' Returns the IDs in an array of LONG <- may change depending on whether
' we save each the device handle for each mouse or the index

' If no mouse found, the ID will just be 0 <- or whatever value we decide as default/none

' *****************************************************************************
' TODO: GET THIS FROM RawInput API
' For now, hardcoded arrRawMouseID(1) to 1, and the rest 0, until we figure out how to do this.
' *****************************************************************************
'Sub GetRawMouseIDs (arrRawMouseID( 8) As Integer)
Sub GetRawMouseIDs ()
    Dim iLoop As Integer

    ' CLEAR OUT IDs
    For iLoop = 1 To 8
        ''arrRawMouseID(iLoop) = 0
        'arrMouse(iLoop).ID = 0
        arrMouse(iLoop).ID = ""
    Next iLoop

    ' GET IDs
    'TODO: get this from RawInput API
    ''arrRawMouseID(1) = 1 ' for now just fudge it!
    'arrMouse(0).ID = 1 ' for now just fudge it!
End Sub ' GetRawMouseIDs

' /////////////////////////////////////////////////////////////////////////////
' Read mouse using RawInput API

' Gets input from mouse, MouseID% = which mouse

' NOTE: click events (mouse up/mouse down) are handled by the calling sub,
'       this routine just sends back
'       TRUE if the given button is currently down or FALSE if it is up.

' Parameters (input only):
' MouseID% = which mouse to return input for
' wheelMin% = minimum value to allow wheelValue% to be decremented to
' wheelMax% = maximum value to allow wheelValue% to be incremened to

' Parameters (values returned):
' x% = mouse x position
' y% = mouse y position
' leftButton% = current state of left mouse button (up or down)
' middleButton% = current state of middle mouse button / scroll wheel button (up or down)
' rightButton% = current state of right mouse button (up or down)
' wheelValue% = value of mouse scroll wheel (passed in and incremented/decremented by 1 if wheel move detected)

Sub ReadRawMouse (MouseID%, x%, y%, leftButton%, middleButton%, rightButton%, wheelValue%, wheelMin%, wheelMax%)
    Dim scrollAmount%
    Dim dx%
    Dim dy%

    ' =============================================================================
    ' BEGIN READ MOUSE THE NEW RawInput WAY:

    ' read scroll wheel
    'TODO: get this from RawInput API

    ' determine mouse x position
    'TODO: get this from RawInput API
    dx% = 0 ' = getMouseDx(MouseID%)
    x% = x% + dx% ' adjust mouse value by dx

    ' determine mouse y position
    'TODO: get this from RawInput API
    dy% = 0 ' = getMouseDy(MouseID%)
    y% = y% + dy% ' adjust mouse value by dx

    ' read mouse buttons
    'TODO: get this from RawInput API
    leftButton% = FALSE
    middleButton% = FALSE
    rightButton% = FALSE

    ' END READ MOUSE THE NEW RawInput WAY:
    ' =============================================================================

    ' =============================================================================
    ' BEGIN READ MOUSE THE OLD QB64 WAY:
    '
    '' read scroll wheel
    'WHILE _MOUSEINPUT ' get latest mouse information
    '    scrollAmount% = _MOUSEWHEEL ' (Returns -1 when scrolling up and 1 when scrolling down with 0 indicating no movement since last read.)
    '    IF (scrollAmount% = -1) AND (wheelValue% > wheelMin%) THEN
    '        wheelValue% = wheelValue% + scrollAmount%
    '    ELSEIF (scrollAmount% = 1) AND (wheelValue% < wheelMax%) THEN
    '        wheelValue% = wheelValue% + scrollAmount%
    '    END IF
    'WEND
    '
    '' determine mouse x position
    'x% = _MOUSEX
    '
    '' determine mouse y position
    'y% = _dy
    '
    '' read mouse buttons
    'leftButton% = _MOUSEBUTTON(1)
    'middleButton% = _MOUSEBUTTON(3)
    'rightButton% = _MOUSEBUTTON(2)
    '
    ' END READ MOUSE THE OLD QB64 WAY:
    ' =============================================================================

End Sub ' ReadRawMouse

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' END MOUSE FUNCTIONS TO COME
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' BEGIN ERROR HANDLING HELPER FUNCTIONS
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Sub ErrorClear
    m_sError = ""
    m_sIncludeError = ""
End Sub ' ErrorClear
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' END ERROR HANDLING HELPER FUNCTIONS
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' BEGIN FILE FUNCTIONS
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' /////////////////////////////////////////////////////////////////////////////
' QB64 Phoenix Edition › QB64 Rising › Code and Stuff › Help Me!
' Using shell to delete a file
' https://qb64phoenix.com/forum/showthread.php?tid=2618&pid=24683#pid24683
' a740g
' #5
' 04-24-2024, 06:05 AM
'
' There are no commands to directly make copies or backup of files.
' But you could write one with a few lines of code like:
'
' Copies src to dst
' Set overwite to true if dst should be overwritten if present
Sub CopyFile (src As String, dst As String, overwrite As _Byte)
    If _FileExists(src) Then
        If Not _FileExists(dst) Or (_FileExists(dst) And overwrite) Then
            _WriteFile dst, _ReadFile$(src)
        End If
    End If
End Sub ' CopyFile

' /////////////////////////////////////////////////////////////////////////////
' Convert a value to string and trim it (because normal Str$ adds spaces)

Function cstr$ (myValue)
    'cstr$ = LTRIM$(RTRIM$(STR$(myValue)))
    cstr$ = _Trim$(Str$(myValue))
End Function ' cstr$

' /////////////////////////////////////////////////////////////////////////////
' QB64 Phoenix Edition › QB64 Rising › Code and Stuff › Help Me!
' Using shell to delete a file
' https://qb64phoenix.com/forum/showthread.php?tid=2618

Sub DeleteFile (sFile As String)
    If _FileExists(sFile) Then
        'Shell "DELETE " + sFile
        'Shell "del " + sFile
        Kill sFile
    End If
End Sub ' DeleteFile

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

Function FileExt$ (sFile As String)
    Dim iPos As Integer
    iPos = _InStrRev(sFile, ".")
    If iPos > 0 Then
        If Len(sFile) > 1 Then
            If iPos > 1 Then
                FileExt$ = Right$(sFile, Len(sFile) - iPos)
            Else
                ' dot is first character, return everything after it
                FileExt$ = Right$(sFile, Len(sFile) - 1)
            End If
        Else
            ' file only has one character, the dot, the file extension is blank
            FileExt$ = ""
        End If
    Else
        ' no dot found, the file extension is blank
        FileExt$ = ""
    End If
End Function ' FileExt$

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

Function NameOnly$ (sFile As String, sSlash As String)
    Dim iPos As Integer
    'sFile = Replace$(sFile, "/", "\")

    iPos = _InStrRev(sFile, sSlash)
    If iPos > 0 Then
        If Len(sFile) > 1 Then
            If iPos > 1 Then
                NameOnly$ = Right$(sFile, Len(sFile) - iPos)
            Else
                ' slash is first character, return everything after it
                NameOnly$ = Right$(sFile, Len(sFile) - 1)
            End If
        Else
            ' file only has one character, the slash, name is blank
            NameOnly$ = ""
        End If
    Else
        ' slash not found, return the entire thing
        NameOnly$ = sFile
    End If
End Function ' NameOnly$

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

Function NoExt$ (sFile As String)
    Dim iPos As Integer
    iPos = _InStrRev(sFile, ".")
    If iPos > 0 Then
        If Len(sFile) > 1 Then
            If iPos > 1 Then
                NoExt$ = Left$(sFile, iPos - 1)
            Else
                ' dot is first character, removing it returns blank!
                ' our version will just return the name unchanged
                ' but you can return blank if you prefer
                NoExt$ = sFile
            End If
        Else
            ' file only has one character, the dot, removing it returns blank!
            ' our version will just return the name unchanged
            ' but you can return blank if you prefer
            NoExt$ = sFile
        End If
    Else
        ' no dot found
        ' return the name unchanged
        NoExt$ = sFile
    End If
End Function ' NoExt$

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

Function PathOnly$ (sFile As String, sSlash As String)
    Dim iPos As Integer
    'sFile = Replace$(sFile, "/", "\")

    iPos = _InStrRev(sFile, sSlash)
    If iPos > 0 Then
        If Len(sFile) > 1 Then
            If iPos > 1 Then
                PathOnly$ = Left$(sFile, iPos)
            Else
                ' slash is first character, so not much of a path, return blank
                PathOnly$ = ""
            End If
        Else
            ' file only has one character, the slash, name is blank
            PathOnly$ = ""
        End If
    Else
        ' slash not found, so not a path, return blank
        PathOnly$ = ""
    End If
End Function ' PathOnly$

' /////////////////////////////////////////////////////////////////////////////
' Writes sText to file sFileName.
' If bAppend=TRUE appends to file, else overwrites it.

' Returns blank if successful else returns error message.

' Example:
' ProgramPath$ = Left$(Command$(0), _InStrRev(Command$(0), "\"))
' ProgramName$: m_ProgramName$ = Mid$(Command$(0), _InStrRev(Command$(0), "\") + 1)
' sFileName = ProgramPath$ + ProgramName$ + ".OUT.txt"
' sText = "This is a test." + chr$(13) + "Here is line 2." + chr$(13) + "End."
' sError = PrintFile$(sFileName, sText, FALSE)

Function PrintFile$ (sFileName As String, sText As String, bAppend As Integer)
    Dim sError As String: sError = ""

    If (bAppend = TRUE) Then
        If _FileExists(sFileName) Then
            Open sFileName For Append As #1 ' opens an existing file for appending
        Else
            sError = "Error in PrintFile$ : File not found. Cannot append."
        End If
    Else
        Open sFileName For Output As #1 ' opens and clears an existing file or creates new empty file
    End If

    If Len(sError) = 0 Then
        ' NOTE: WRITE places text in quotes in the file
        'WRITE #1, x, y, z$
        'WRITE #1, sText

        ' PRINT does not put text inside quotes
        Print #1, sText

        Close #1
    End If

    PrintFile$ = sError
End Function ' PrintFile$

' /////////////////////////////////////////////////////////////////////////////
' Fastest way is always to just read the whole life at once and then parse it.

Function ReadTextFile$ (sFileName As String, sDefault As String)
    Dim x$
    If _FileExists(sFileName) Then
        Open sFileName For Binary As #1
        x$ = Space$(LOF(1))
        Get #1, 1, x$
        Close #1
        ReadTextFile$ = x$
    Else
        ReadTextFile$ = sDefault
    End If
End Function ' ReadTextFile$

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' END FILE FUNCTIONS
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' BEGIN GENERAL PURPOSE FUNCTIONS
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' /////////////////////////////////////////////////////////////////////////////
' FOR BITWISE OPERATIONS

Function HasBit% (iByte As Integer, iBit As Integer)
    ''TODO: precalculate
    'dim shared m_arrBitValue(1 To 8) As Integer
    'dim iLoop as Integer
    'For iLoop = 0 To 7
    '   m_arrBitValue(iLoop + 1) = 2 ^ iLoop
    'Next iLoop
    'HasBit% = ((iByte And m_arrBitValue(iBit)) = m_arrBitValue(iBit))
    Dim iBitValue As Integer
    iBitValue = 2 ^ (iBit - 1)
    HasBit% = ((iByte And iBitValue) = iBitValue)
End Function ' HasBit%

' /////////////////////////////////////////////////////////////////////////////
' Returns TRUE if value OriginalString$ is numeric.

' Re: Does a Is Number function exist in QB64?
' https://www.qb64.org/forum/index.php?topic=896.15

' Version 2 by madscijr
' Returns TRUE (-1) if string is an integer, FALSE (0) if not

' Version 1 by MWheatley
' Reply #18 on: January 01, 2019, 11:24:30 AM
' returns 1 if string is an integer, 0 if not

Function IsNumber% (OriginalString$)
    Dim bResult%: bResult% = FALSE
    Dim iLoop%
    Dim TestString$
    'Dim bNegative%
    Dim iDecimalCount%
    Dim sNextChar$

    'THEY SHOULD TRIM OUTSIDE THE FUNCTION!
    'TestString$ = _TRIM$(OriginalString$)

    If Len(OriginalString$) > 0 Then
        TestString$ = ""
        If Left$(OriginalString$, 1) = "+" Then
            TestString$ = Right$(OriginalString$, Len(OriginalString$) - 1)
            'bNegative% = FALSE
        ElseIf Left$(OriginalString$, 1) = "-" Then
            TestString$ = Right$(OriginalString$, Len(OriginalString$) - 1)
            'bNegative% = TRUE
        Else
            TestString$ = OriginalString$
            'bNegative% = FALSE
        End If
        If Len(TestString$) > 0 Then
            bResult% = TRUE
            iDecimalCount% = 0
            For iLoop% = 1 To Len(TestString$)
                sNextChar$ = Mid$(TestString$, iLoop%, 1)
                If sNextChar$ = "." Then
                    iDecimalCount% = iDecimalCount% + 1
                    If iDecimalCount% > 1 Then
                        ' TOO MANY DECIMAL POINTS, INVALID!
                        bResult% = FALSE
                        Exit For
                    End If
                ElseIf Asc(sNextChar$) < 48 Or Asc(sNextChar$) > 57 Then
                    ' NOT A NUMERAL OR A DECIMAL, INVALID!
                    bResult% = FALSE
                    Exit For
                End If
            Next iLoop%
        End If
    End If
    IsNumber% = bResult%
End Function ' IsNumber%

' /////////////////////////////////////////////////////////////////////////////
' Split and join strings
' https://www.qb64.org/forum/index.php?topic=1073.0

'Combine all elements of in$() into a single string with delimiter$ separating the elements.

Function join$ (in$(), delimiter$)
    Dim result$
    Dim iLoop%
    result$ = in$(LBound(in$))
    For iLoop% = LBound(in$) + 1 To UBound(in$)
        result$ = result$ + delimiter$ + in$(iLoop%)
    Next iLoop%
    join$ = result$
End Function ' join$

' /////////////////////////////////////////////////////////////////////////////
' Split and join strings
' https://www.qb64.org/forum/index.php?topic=1073.0
'
' FROM luke, QB64 Developer
' Date: February 15, 2019, 04:11:07 AM
'
' Given a string of words separated by spaces (or any other character),
' splits it into an array of the words. I've no doubt many people have
' written a version of this over the years and no doubt there's a million
' ways to do it, but I thought I'd put mine here so we have at least one
' version. There's also a join function that does the opposite
' array -> single string.
'
' Code is hopefully reasonably self explanatory with comments and a little demo.
' Note, this is akin to Python/JavaScript split/join, PHP explode/implode.

'Split in$ into pieces, chopping at every occurrence of delimiter$. Multiple consecutive occurrences
'of delimiter$ are treated as a single instance. The chopped pieces are stored in result$().
'
'delimiter$ must be one character long.
'result$() must have been REDIMmed previously.

' Modified to handle multi-character delimiters

Sub split (in$, delimiter$, result$())
    Dim start As Integer
    Dim finish As Integer
    Dim iDelimLen As Integer
    ReDim result$(-1)

    iDelimLen = Len(delimiter$)

    start = 1
    Do
        'While Mid$(in$, start, 1) = delimiter$
        While Mid$(in$, start, iDelimLen) = delimiter$
            'start = start + 1
            start = start + iDelimLen
            If start > Len(in$) Then
                Exit Sub
            End If
        Wend
        finish = InStr(start, in$, delimiter$)
        If finish = 0 Then
            finish = Len(in$) + 1
        End If

        ReDim _Preserve result$(0 To UBound(result$) + 1)

        result$(UBound(result$)) = Mid$(in$, start, finish - start)
        start = finish + 1
    Loop While start <= Len(in$)
End Sub ' split

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' END GENERAL PURPOSE FUNCTIONS
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' ################################################################################################################################################################
' #REFERENCE

' =============================================================================
' SOME USEFUL STUFF FOR REFERENCE:

' Type Name               Type suffix symbol   Minimum value                  Maximum value                Size in Bytes
' ---------------------   ------------------   ----------------------------   --------------------------   -------------
' _BIT                    `                    -1                             0                            1/8
' _BIT * n                `n                   -128                           127                          n/8
' _UNSIGNED _BIT          ~`                   0                              1                            1/8
' _BYTE                   %%                   -128                           127                          1
' _UNSIGNED _BYTE         ~%%                  0                              255                          1
' INTEGER                 %                    -32,768                        32,767                       2
' _UNSIGNED INTEGER       ~%                   0                              65,535                       2
' LONG                    &                    -2,147,483,648                 2,147,483,647                4
' _UNSIGNED LONG          ~&                   0                              4,294,967,295                4
' _INTEGER64              &&                   -9,223,372,036,854,775,808     9,223,372,036,854,775,807    8
' _UNSIGNED _INTEGER64    ~&&                  0                              18,446,744,073,709,551,615   8
' SINGLE                  ! or none            -2.802597E-45                  +3.402823E+38                4
' DOUBLE                  #                    -4.490656458412465E-324        +1.797693134862310E+308      8
' _FLOAT                  ##                   -1.18E-4932                    +1.18E+4932                  32(10 used)
' _OFFSET                 %&                   -9,223,372,036,854,775,808     9,223,372,036,854,775,807    Use LEN
' _UNSIGNED _OFFSET       ~%&                  0                              18,446,744,073,709,551,615   Use LEN
' _MEM                    none                 combined memory variable type  N/A                          Use LEN

' div: int1% = num1% \ den1%
' mod: rem1% = num1% MOD den1%

' @END

"readmicesub34c.bas"  compiles OK:
Code: (Select All)
' ################################################################################################################################################################
' Multimouse sub-program "ReadMiceSub.bas"
' ################################################################################################################################################################

' -------------------------------------------------------------------------------
' ABOUT
' -------------------------------------------------------------------------------
' (Subprogram for READMICEMAIN.BAS, see that for more info.)

Option Explicit
_Title "readmice"
$NoPrefix
'$Console:Only
'Console Off

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' BEGIN CONSTANTS
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Const cProgName = "ReadMiceSub"
Const FALSE = 0
Const TRUE = Not FALSE

Const cInvisible = 0
Const cTransparent = 160
Const cVisible = 255

' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
' FOR CONTROLLING WINDOW ON TOP, ETC.
Const SWP_NOSIZE = &H0001 'ignores cx and cy size parameters
'Const SWP_NOMOVE = &H0002 'ignores x and y position parameters
'Const SWP_NOZORDER = &H0004 'keeps z order and ignores hWndInsertAfter parameter
'Const SWP_NOREDRAW = &H0008 'does not redraw window changes
Const SWP_NOACTIVATE = &H0010 'does not activate window
'Const SWP_FRAMECHANGED = &H0020
'Const SWP_SHOWWINDOW = &H0040
'Const SWP_HIDEWINDOW = &H0080
'Const SWP_NOCOPYBITS = &H0100
'Const SWP_NOOWNERZORDER = &H0200
'Const SWP_NOSENDCHANGING = &H0400
'Const SWP_DRAWFRAME = SWP_FRAMECHANGED
'Const SWP_NOREPOSITION = SWP_NOOWNERZORDER
'Const SWP_DEFERERASE = &H2000
'Const SWP_ASYNCWINDOWPOS = &H4000
Const HWND_TOP = 0 'window at top of z order no focus
Const HWND_BOTTOM = 1 'window at bottom of z order no focus
Const HWND_TOPMOST = -1 'window above all others no focus unless active
Const HWND_NOTOPMOST = -2 'window below active no focus

' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
' FOR RAW INPUT API
Const CS_HREDRAW = &H0002
Const CS_VREDRAW = &H0001

Const IDI_APPLICATION = 32512
Const IDC_ARROW = 32512
Const COLOR_WINDOW = 5

Const WS_OVERLAPPED = &H00000000
Const WS_CAPTION = &H00C00000
Const WS_SYSMENU = &H00080000
Const WS_THICKFRAME = &H00040000
Const WS_MINIMIZEBOX = &H00020000
Const WS_MAXIMIZEBOX = &H00010000
Const WS_CHILD = &H40000000
Const WS_VISIBLE = &H10000000
Const WS_OVERLAPPEDWINDOW = WS_OVERLAPPED Or WS_CAPTION Or WS_SYSMENU Or WS_THICKFRAME Or WS_MINIMIZEBOX Or WS_MAXIMIZEBOX
Const CW_USEDEFAULT = &H80000000

Const WM_DESTROY = &H0002
Const WM_INPUT = &H00FF

Const SW_SHOW = 5

Const RID_INPUT = &H10000003

Const RIM_TYPEMOUSE = 0 ' Raw input comes from the mouse.
Const RIM_TYPEKEYBOARD = 1 ' Raw input comes from the keyboard.
Const RIM_TYPEHID = 2 ' Raw input comes from some device that is not a keyboard or a mouse.

Const MOUSE_MOVE_RELATIVE = &H00
Const MOUSE_MOVE_ABSOLUTE = &H01
Const MOUSE_VIRTUAL_DESKTOP = &H02
Const MOUSE_ATTRIBUTES_CHANGED = &H04
Const MOUSE_MOVE_NOCOALESCE = &H08

Const WM_MOUSEMOVE = &H0200

Const WM_PAINT = &H000F

Const DT_CENTER = &H00000001

' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
' MIN/MAX VALUES FOR MOUSE TEST
Const cMinX = 1
Const cMaxX = 80
Const cMinY = 1
Const cMaxY = 30 ' 24
Const cMinWheel = 0
Const cMaxWheel = 255

' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
' CONSTANT FOR 2ND DIMENSION OF arrFile ARRAY
Const cFileName = 0
Const cFileData = 1

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' END CONSTANTS
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' BEGIN UDTs
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Type RAWINPUTDEVICE
    As Unsigned Integer usUsagePage, usUsage
    As Unsigned Long dwFlags
    As Offset hwndTarget ' <- WHAT IS Offset VS _Offset ?
End Type

Type RAWINPUTDEVICELIST
    As Offset hDevice
    As Unsigned Long dwType
    $If 64BIT Then
        As String * 4 alignment
    $End If
End Type

Type POINT
    As Long x, y
End Type

Type MSG
    As Offset hwnd
    As Unsigned Long message
    As Unsigned Offset wParam
    As Offset lParam
    As Long time
    As POINT pt
    As Long lPrivate
End Type

Type WNDCLASSEX
    As Unsigned Long cbSize, style
    As Offset lpfnWndProc
    As Long cbClsExtra, cbWndExtra
    As Offset hInstance, hIcon, hCursor, hbrBackground, lpszMenuName, lpszClassName, hIconSm
End Type

Type RECT
    As Long left, top, right, bottom
End Type

Type PAINTSTRUCT
    As Offset hdc
    As Long fErase
    $If 64BIT Then
        As String * 4 alignment
    $End If
    As RECT rcPaint
    As Long fRestore, fIncUpdate
    As String * 32 rgbReserved
End Type

Type RAWINPUTHEADER
    As Unsigned Long dwType, dwSize
    As Offset hDevice
    As Unsigned Offset wParam
End Type

Type RAWMOUSE
    As Unsigned Integer usFlags
    $If 64BIT Then
        As String * 2 alignment
    $End If
    'As Unsigned Long ulButtons  'commented out because I'm creating this value using MAKELONG
    As Unsigned Integer usButtonFlags, usButtonData
    As Unsigned Long ulRawButtons
    As Long lLastX, lLastY
    As Unsigned Long ulExtraInformation
End Type

Type RAWINPUT
    As RAWINPUTHEADER header
    As RAWMOUSE mouse
End Type

' UDT TO HOLD THE INFO FOR EACH MOUSE
Type MouseInfoType
    UpdateCount As Integer ' if this value changes we know a value changed
    ID As String ' mouse device ID
    c As String ' cursor character
    x As Integer ' screen x position
    y As Integer ' screen y position
    dx As Integer ' mouse x movement -1=left, 1=right, 0=none
    dy As Integer ' mouse y movement -1=up  , 1=down , 0=none
    wheel As Integer ' mouse wheel value
    LeftDown As Integer ' tracks left mouse button state, TRUE=down
    MiddleDown As Integer ' tracks middle mouse button state, TRUE=down
    RightDown As Integer ' tracks right mouse button state, TRUE=down
    LeftCount As Integer ' counts left clicks
    MiddleCount As Integer ' counts middle clicks
    RightCount As Integer ' counts right clicks
End Type ' MouseInfoType

' UDT TO HOLD THE INFO FOR EACH KEYBOARD
Type KeyboardInfoType
    UpdateCount As Integer ' if this value changes we know a value changed
    ID As String ' keyboard device ID
    'TBD
End Type ' KeyboardInfoType

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' END UDTs
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' BEGIN API DECLARATIONS
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
' FOR RAW INPUT API
' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
Declare CustomType Library
    Function GetRawInputDeviceList~& (ByVal pRawInputDeviceList As Offset, Byval puiNumDevices As Offset, Byval cbSize As Unsigned Long)
    Sub GetRawInputDeviceList (ByVal pRawInputDeviceList As Offset, Byval puiNumDevices As Offset, Byval cbSize As Unsigned Long)
    Function RegisterRawInputDevices& (ByVal pRawInputDevices As Offset, Byval uiNumDevices As Unsigned Long, Byval cbSize As Unsigned Long)
    Function GetModuleHandle%& (ByVal lpModulename As Offset)
    Function LoadIcon%& (ByVal hInstance As Offset, Byval lpIconName As Offset)
    Function LoadCursor%& (ByVal hInstance As Offset, Byval lpCursorName As Offset)
    Function RegisterClassEx~% (ByVal wndclassex As Offset)
    Function CreateWindowEx%& (ByVal dwExStyle As Unsigned Long, Byval lpClassName As Offset, Byval lpWindowName As Offset, Byval dwStyle As Unsigned Long, Byval x As Long, Byval y As Long, Byval nWidth As Long, Byval nHeight As Long, Byval hWndParent As Offset, Byval hMenu As Offset, Byval hInstance As Offset, Byval lpParam As Offset)
    Sub ShowWindow (ByVal hWnd As Offset, Byval nCmdShow As Long)
    Sub UpdateWindow (ByVal hWnd As Offset)
    Function GetMessage& (ByVal lpMsg As Offset, Byval hWnd As Offset, Byval wMsgFilterMin As Unsigned Long, Byval wMsgFilterMax As Unsigned Long)
    Sub TranslateMessage (ByVal lpMsg As Offset)
    Sub DispatchMessage (ByVal lpMsg As Offset)
    Sub PostQuitMessage (ByVal nExitCode As Long)
    Function DefWindowProc%& (ByVal hWnd As Offset, Byval Msg As Unsigned Long, Byval wParam As Unsigned Offset, Byval lParam As Offset)
    Sub GetRawInputData (ByVal hRawInput As Offset, Byval uiCommand As Unsigned Long, Byval pData As Offset, Byval pcbSize As Offset, Byval cbSizeHeader As Unsigned Long)
    Function GetRawInputData~& (ByVal hRawInput As Offset, Byval uiCommand As Unsigned Long, Byval pData As Offset, Byval pcbSize As Offset, Byval cbSizeHeader As Unsigned Long)
    Sub InvalidateRect (ByVal hWnd As Offset, Byval lpRect As Offset, Byval bErase As Long)
    Sub SendMessage (ByVal hWnd As Offset, Byval Msg As Unsigned Long, Byval wParam As Unsigned Offset, Byval lParam As Offset)
    Function BeginPaint%& (ByVal hWnd As Offset, Byval lpPaint As Offset)
    Sub GetClientRect (ByVal hWnd As Offset, Byval lpRect As Offset)
    Sub DrawText (ByVal hdc As Offset, Byval lpchText As Offset, Byval cchText As Long, Byval lprc As Offset, Byval format As Unsigned Long)
    Sub OffsetRect (ByVal lprc As Offset, Byval dx As Long, Byval dy As Long)
    Sub EndPaint (ByVal hWnd As Offset, Byval lpPaint As Offset)
End Declare

' Header file "makeint.h" must be in same folder as this program.
Declare CustomType Library ".\makeint"
    Function MAKEINTRESOURCE%& Alias "MAKEINTRSC" (ByVal i As _Offset)
End Declare

Declare Library
    Function MAKELPARAM%& (ByVal l As Integer, Byval h As Integer)
    Function MAKELONG~& (ByVal l As Unsigned Integer, Byval h As Unsigned Integer)
End Declare

$If 64BIT Then
    Declare Library ".\internal\c\c_compiler\x86_64-w64-mingw32\include\windowsx"
    $Else
    Declare Library ".\internal\c\c_compiler\i686-w64-mingw32\include\windowsx"
    $End If
    Function GET_Y_LPARAM& (ByVal lp As Offset)
    Function GET_X_LPARAM& (ByVal lp As Offset)
End Declare

' Header file "winproc.h" must be in same folder as this program.
Declare Library ".\winproc"
    Function WindowProc%& ()
End Declare

' ----------------------------------------------------------------------------------------------------------------------------------------------------------------

' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
' To keep focus on window
' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
'Declare Dynamic Library "user32"
'    Sub ShowWindow (ByVal hWnd As _Offset, Byval nCmdShow As Long)
'End Declare

' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
' To make window invisible
' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
Declare Dynamic Library "user32"
    Function SetLayeredWindowAttributes& (ByVal hwnd As _Offset, Byval crKey As Long, Byval bAlpha As _Unsigned _Byte, Byval dwFlags As Long)
    Function GetWindowLong& Alias "GetWindowLongA" (ByVal hwnd As _Offset, Byval nIndex As Long)
    Function SetWindowLong& Alias "SetWindowLongA" (ByVal hwnd As _Offset, Byval nIndex As Long, Byval dwNewLong As Long)
End Declare

' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
' Needed for acquiring the hWnd of the window
' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
Declare Library
    Function FindWindow& (ByVal ClassName As _Offset, WindowName$) ' To get hWnd handle
End Declare
' ----------------------------------------------------------------------------------------------------------------------------------------------------------------


' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' END API DECLARATIONS
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' BEGIN GLOBAL VARIABLES
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' BASIC PROGRAM METADATA
Dim Shared m_ProgramPath$: m_ProgramPath$ = Left$(Command$(0), _InStrRev(Command$(0), "\"))
Dim Shared m_ProgramName$: m_ProgramName$ = Mid$(Command$(0), _InStrRev(Command$(0), "\") + 1)
Dim Shared m_sTriggerFile As String: m_sTriggerFile = m_ProgramPath$ + "ReadMiceSub.DELETE-TO-CLOSE"

' GLOBAL VARIABLES TO TRACK ERROR STATE
Dim Shared m_sError As String: m_sError = ""
Dim Shared m_sIncludeError As String: m_sIncludeError = ""

' RAW INPUT VARIABLES
Dim Shared mousemessage As String
Dim Shared rawinputdevices As String

' MOUSE VARIABLES
Dim Shared arrMouse(0 To 8) As MouseInfoType ' STORES INFO FOR EACH MOUSE
'Dim Shared arrRawMouseID(8) As Long ' device IDs for mice connected to system (guessing this would be a string, dunno)
Dim Shared iMouseCount As Integer ' # OF MICE ATTACHED

' KEYBOARD VARIABLES
Dim Shared arrKeyboard(0 To 8) As KeyboardInfoType ' STORES INFO FOR EACH KEYBOARD
Dim Shared iKeyboardCount As Integer ' # OF KEYBOARDS ATTACHED
Dim Shared arrKeyState(0 To 8, 1 To 512) As Integer ' arrKeyState({device#}, {keyCode}) = TRUE if key {keyCode} on keyboard {device#} is currently held down.

Dim Shared arrScreen(1 To 80, 1 To 25) As String ' STORES TEXT FOR SCREEN

Dim Shared iMinX As Long
Dim Shared iMaxX As Long
Dim Shared iMinY As Long
Dim Shared iMaxY As Long

' RAW FILE NAMES
Dim Shared arrFile(0 To 31, 0 To 1) As String

' NETWORK VARIABLES
Dim Shared uintPort As _Unsigned Integer ' port
Dim Shared lngConn As Long ' c&
Dim Shared iData As Integer ' i
Dim Shared sOutput As String ' s$

' HANDLE FOR THE PROGRAM WINDOW
Dim Shared MyHwnd As _Offset ' _Integer64 hwnd%&
'Dim As Offset hwndMain
Dim Shared hwndMain As _Offset

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' END GLOBAL VARIABLES
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' EXECUTION STARTS HERE!
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' =============================================================================
' START THE MAIN ROUTINE
main

' =============================================================================
' FINISH
'Print m_ProgramName$ + " finished."
'End
System ' return control to the operating system

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' BEGIN GLOBAL ERROR HANDLER
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ErrorHandler:
m_sError = "Error #" + _Trim$(Str$(Err)) + " at line " + _Trim$(Str$(_ErrorLine)) + "."
m_sIncludeError = "File " + Chr$(34) + _InclErrorFile$ + Chr$(34) + " at line " + _Trim$(Str$(_InclErrorLine)) + "."
Resume Next
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' END GLOBAL ERROR HANDLER
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' BEGIN DATA STATEMENTS
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' MOUSE CURSORS (JUST SOME LETTERS)
CData:
Data A,b,C,D,E,f,G,H

' DEFAULT/INTIAL X COORDINATE OF EACH CURSOR ON SCREEN
XData:
Data 5,15,25,35,45,55,65,75

' DEFAULT/INTIAL Y COORDINATE OF EACH CURSOR ON SCREEN
YData:
Data 17,17,19,19,21,21,23,23

' DEFAULT/INITIAL VALUE OF EACH SCROLL WHEEL
WData:
Data 224,192,160,128,96,64,32,0
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' END DATA STATEMENTS
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' BEGIN MAIN ROUTINES
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

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

Sub main
    Dim sPort As String
    Dim iLoop As Integer
    Dim in$

    ' MAKE SURE WE HAVE INPUT
    sPort = Command$(1)
    If Len(sPort) > 0 Then
        If IsNumber%(sPort) = TRUE Then
            ' OPEN CONNECTION
            uintPort = Val(sPort)
            lngConn = _OpenClient("tcp/ip:" + _Trim$(Str$(uintPort)) + ":localhost")
            Print lngConn



            ' ****************************************************************************************************************************************************************
            ' ****************************************************************************************************************************************************************
            ' RETHINK DATA STRUCTURE
            ' ****************************************************************************************************************************************************************
            ' ****************************************************************************************************************************************************************
            ' INITIALIZE
            For iLoop = LBound(arrFile) To UBound(arrFile)
                arrFile(iLoop, cFileName) = m_ProgramPath$ + "mouse" + _Trim$(Str$(iLoop)) + ".txt"
                arrFile(iLoop, cFileData) = ""
            Next iLoop

            ' INITIALIZE
            iMinX = 0
            iMaxX = 3583
            iMinY = 0
            iMaxY = 8202

            ' ****************************************************************************************************************************************************************
            ' ****************************************************************************************************************************************************************
            ' SET UP WINDOW TO BE SAME SIZE AS, AND OVERLAPPED WITH HOST WINDOW
            ' ****************************************************************************************************************************************************************
            ' ****************************************************************************************************************************************************************
            ' SET UP WINDOW
            'Screen _NewImage(1024, 768, 32)
            Screen 12 ' SCREEN 12 can use 16 color attributes with a black background. 256K possible RGB color hues. Background colors can be used with QB64.

            ' window needs to be lined up directly under the main program, so the mouse coordinates align with the display
            _ScreenMove 0, 0 ' <<< NOT WORKING, HOW DO WE DO THIS IN THE EVENT MODEL?



            ' CREATE TRIGGER FILE
            Open m_sTriggerFile For Output As #1
            Print #1, "Deleting this file will cause program " + m_ProgramName$ + " to stop running."
            Close #1

            ' GET HANDLE TO THE PROGRAM WINDOW
            Do
                MyHwnd = _WindowHandle
            Loop Until MyHwnd




            ' GIVE CONTROL TO THE EVENT-ORIENTED CODE
            System Val(Str$(WinMain))



        Else
            Print "Invalid non-numeric input " + Chr$(34) + sPort + Chr$(34) + ". Exiting."
        End If
    Else
        Print "No input. Exiting."
    End If

End Sub ' main

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' END MAIN ROUTINES
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' BEGIN RAW INPUT FUNCTIONS
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' /////////////////////////////////////////////////////////////////////////////
' Runs first

Function WinMain~%& ()
    'Dim As Offset hwndMain
    Dim As Offset hInst
    Dim As Offset hWndTop

    Dim As MSG msg
    Dim As WNDCLASSEX wndclass
    Dim As String szMainWndClass
    Dim As String szWinTitle
    Dim As Unsigned Integer reg
    Dim sData As String

    'DEBUG: TRY FULL SCREEN <- PROGRAM CRASHES!
    '_FullScreen _SquarePixels

    hInst = GetModuleHandle(0)
    szMainWndClass = "WinTestWin" + Chr$(0)
    'szWinTitle = "Hello" + Chr$(0)
    szWinTitle = cProgName + Chr$(0)

    wndclass.lpszClassName = Offset(szMainWndClass)
    wndclass.cbSize = Len(wndclass)
    wndclass.style = CS_HREDRAW Or CS_VREDRAW
    wndclass.lpfnWndProc = WindowProc
    wndclass.hInstance = hInst 'GetModuleHandle(0) will return the hInstance of this EXE
    wndclass.hIcon = LoadIcon(0, MAKEINTRESOURCE(IDI_APPLICATION))
    wndclass.hIconSm = LoadIcon(0, MAKEINTRESOURCE(IDI_APPLICATION))
    wndclass.hCursor = LoadCursor(0, MAKEINTRESOURCE(IDC_ARROW))
    wndclass.hbrBackground = COLOR_WINDOW + 1

    reg = RegisterClassEx(Offset(wndclass)) 'I prefer to use the output of RegisterClassEx rather than the window name

    'DEBUG: SUBSTITUTE _WindowHandle

    'Function  CreateWindowEx%& (
    '   ByVal dwExStyle    As Unsigned Long = 0
    '   Byval lpClassName  As Offset        = MAKELPARAM(reg, 0)
    '   Byval lpWindowName As Offset        = Offset(szWinTitle)
    '   Byval dwStyle      As Unsigned Long = WS_OVERLAPPEDWINDOW
    '   Byval x            As Long          = CW_USEDEFAULT
    '   Byval y            As Long          = CW_USEDEFAULT
    '   Byval nWidth       As Long          = CW_USEDEFAULT
    '   Byval nHeight      As Long          = CW_USEDEFAULT
    '   Byval hWndParent   As Offset        = 0
    '   Byval hMenu        As Offset        = 0
    '   Byval hInstance    As Offset        = hInst
    '   Byval lpParam      As Offset        = 0

    '    hwndMain = CreateWindowEx( _
    '       0, _
    '       MAKELPARAM(reg, 0), _
    '       Offset(szWinTitle), _
    '       WS_OVERLAPPEDWINDOW, _
    '       CW_USEDEFAULT, _
    '       CW_USEDEFAULT, _
    '       CW_USEDEFAULT, _
    '       CW_USEDEFAULT, _
    '       0, _
    '       0, _
    '       hInst, _
    '       0)

    hwndMain = CreateWindowEx( _
        0, _
        MAKELPARAM(reg, 0), _
        Offset(szWinTitle), _
        WS_OVERLAPPEDWINDOW, _
        0, _
        0, _
        1024, _
        768, _
        0, _
        0, _
        hInst, _
        0)

    'hwndMain = _WindowHandle
    ShowWindow hwndMain, SW_SHOW


    ' TURN SUB WINDOW INVISIBLE
    ''SetWindowOpacity MyHwnd, cInvisible
    'SetWindowOpacity hwndMain, cInvisible


    ' KEEP WINDOW VISIBLE
    UpdateWindow hwndMain
    'DEBUG: SUBSTITUTE _WindowHandle
    'UpdateWindow _WindowHandle


    '' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
    '' SEND SUB WINDOW HANDLE BACK TO MAIN
    'sData = _Trim$(Str$(hwndMain))
    'Put #lngConn, , sData
    '' ----------------------------------------------------------------------------------------------------------------------------------------------------------------





    ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
    ' START THE INPUT ROUTINES
    ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
    InitRawInput
    InitMouseTest 'TODO: SAVE_MOUSE_INFO

    ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
    ' MAIN LOOP
    ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
    While GetMessage(Offset(msg), 0, 0, 0)
        TranslateMessage Offset(msg)
        DispatchMessage Offset(msg)


        ' QUIT IF TRIGGER FILE IS GONE
        If _FileExists(m_sTriggerFile) = FALSE Then
            System
        End If



        ' SEE IF WE CAN DETECT KEYPRESSES
        ' IF USER PRESSES A THEN SHOW WINDOW
        If _KeyDown(65) Or _KeyDown(97) Then
            Beep
            'SetWindowOpacity MyHwnd, cVisible
        End If

        ' IF USER PRESSES B THEN MAKE WINDOW TRANSPARENT
        If _KeyDown(66) Or _KeyDown(98) Then
            Beep
            Beep
            'SetWindowOpacity MyHwnd, cTransparent
        End If

        ' IF USER PRESSES C THEN HIDE WINDOW
        If _KeyDown(67) Or _KeyDown(99) Then
            Beep
            Beep
            Beep
            'SetWindowOpacity MyHwnd, cInvisible
        End If

        ' IF USER PRESSES ESCAPE THEN EXIT
        If _KeyDown(27) Then
            DeleteFile m_sTriggerFile
            'System
        End If

        ' KEEP WINDOW ON TOP
        If _WindowHasFocus = 0 Then
            _ScreenIcon
            ''ShowWindow MyHwnd, 1
            'ShowWindow hwndMain, 1
            ShowWindow hwndMain, SW_SHOW
        End If




    Wend




    WinMain = msg.wParam
End Function ' WinMain

' /////////////////////////////////////////////////////////////////////////////
' Handles main window events

Function MainWndProc%& (hwnd As Offset, nMsg As Unsigned Long, wParam As Unsigned Offset, lParam As Offset)
    Static As Offset hwndButton
    Static As Long cx, cy
    Dim As Offset hdc
    Dim As PAINTSTRUCT ps
    Dim As RECT rc
    Dim As MEM lpb
    Dim As Unsigned Long dwSize
    Dim As RAWINPUT raw
    Dim As Long tmpx, tmpy
    Static As Long maxx
    Dim As RAWINPUTHEADER rih

    ' TEMP VARIABLES FOR DISPLAYING FORMATTED VALUES TO SCREEN
    Dim strNextID As String
    Dim iIndex As Integer
    Dim iRowOffset As Integer
    Dim iLen As Integer
    Dim sCount As String
    Dim sX As String
    Dim sY As String
    Dim sWheel As String
    Dim sLeftDown As String
    Dim sMiddleDown As String
    Dim sRightDown As String
    Dim sLeftCount As String
    Dim sMiddleCount As String
    Dim sRightCount As String
    Dim sNext As String
    Dim iNewX As Integer
    Dim iNewY As Integer
    Dim iDX As Integer
    Dim iDY As Integer

    ' MORE TEMP VARIABLES
    Dim iMouseNum As Integer

    ' HANDLE EVENTS
    Select Case nMsg
        Case WM_DESTROY
            PostQuitMessage 0
            MainWndProc = 0
            Exit Function

        Case WM_INPUT
            GetRawInputData lParam, RID_INPUT, 0, Offset(dwSize), Len(rih)
            lpb = MemNew(dwSize)
            If lpb.SIZE = 0 Then
                MainWndProc = 0
                Exit Function
            End If
            If GetRawInputData(lParam, RID_INPUT, lpb.OFFSET, Offset(dwSize), Len(rih)) <> dwSize Then
                'Print "GetRawInputData doesn't return correct size!"
                mousemessage = "GetRawInputData doesn't return correct size!"
            End If
            MemGet lpb, lpb.OFFSET, raw

            If raw.header.dwType = RIM_TYPEMOUSE Then
                tmpx = raw.mouse.lLastX
                tmpy = raw.mouse.lLastY
                maxx = tmpx

                ' GET MOUSE INFO
                ' NOTES:
                ' ulButtons and usButtonFlags both return the same thing (buttons)
                ' usButtonData changes value when scroll wheel moved (just stays at one value)
                'mousemessage = ""
                'mousemessage = mousemessage + "Mouse:hDevice" + Str$(raw.header.hDevice)
                'mousemessage = mousemessage + "usFlags=" + Hex$(raw.mouse.usFlags)
                'mousemessage = mousemessage + "ulButtons=" + Hex$(MAKELONG(raw.mouse.usButtonFlags, raw.mouse.usFlags))
                'mousemessage = mousemessage + "usButtonFlags=" + Hex$(raw.mouse.usButtonFlags)
                'mousemessage = mousemessage + "usButtonData=" + Hex$(raw.mouse.usButtonData)
                'mousemessage = mousemessage + "ulRawButtons=" + Hex$(raw.mouse.ulRawButtons)
                'mousemessage = mousemessage + "lLastX=" + Str$(raw.mouse.lLastX)
                'mousemessage = mousemessage + "lLastY=" + Str$(raw.mouse.lLastY)
                'mousemessage = mousemessage + "ulExtraInformation=" + Hex$(raw.mouse.ulExtraInformation) + Chr$(13)

                ' UPDATE RANGE OF MOUSE COORDINATES
                If GET_X_LPARAM(lParam) < iMinX Then iMinX = GET_X_LPARAM(lParam)
                If GET_X_LPARAM(lParam) > iMaxX Then iMaxX = GET_X_LPARAM(lParam)
                If GET_Y_LPARAM(lParam) < iMinY Then iMinY = GET_Y_LPARAM(lParam)
                If GET_Y_LPARAM(lParam) > iMaxY Then iMaxY = GET_Y_LPARAM(lParam)

                ' IDENTIFY WHICH MOUSE IT IS
                strNextID = _Trim$(Str$(raw.header.hDevice))
                iIndex = GetMouseIndex%(strNextID)
                If iIndex >= LBound(arrMouse) Then
                    If iIndex <= UBound(arrMouse) Then

                        ' =============================================================================
                        ' READ MOUSE MOVEMENT

                        ' DOESN'T WORK, MOVES ALL OVER THE PLACE:
                        '' METHOD #1: SCALE MOUSE POSITION TO 80X25 POSITION
                        'iNewX = ( (GET_X_LPARAM(lParam) + 1) * 80) \ (iMaxX+1)
                        'iNewY = ( (GET_Y_LPARAM(lParam) + 1) * 25) \ (iMaxY+1)
                        'arrMouse(iIndex).x = iNewX
                        'arrMouse(iIndex).y = iNewY

                        ' WORKS BUT NOT THAT ACCURATE:
                        ' METHOD #2: INCREMENT/DECREMENT DELTA
                        If raw.mouse.lLastX < 0 Then
                            arrMouse(iIndex).dx = -1
                        ElseIf raw.mouse.lLastX > 0 Then
                            arrMouse(iIndex).dx = 1
                        Else
                            arrMouse(iIndex).dx = 0
                        End If

                        If raw.mouse.lLastY < 0 Then
                            arrMouse(iIndex).dy = -1
                        ElseIf raw.mouse.lLastY > 0 Then
                            arrMouse(iIndex).dy = 1
                        Else
                            arrMouse(iIndex).dy = 0
                        End If

                        ' =============================================================================
                        'TODO: SAVE SCROLL WHEEL + BUTTONS
                        'Hex$(raw.mouse.usButtonFlags)

                        ' left button = 1 when down, 2 when released
                        If ((raw.mouse.usButtonFlags And 1) = 1) Then
                            arrMouse(iIndex).LeftDown = TRUE
                        ElseIf ((raw.mouse.usButtonFlags And 2) = 2) Then
                            arrMouse(iIndex).LeftDown = FALSE
                        End If

                        ' middle button = 16 when down, 32 when released
                        If ((raw.mouse.usButtonFlags And 16) = 16) Then
                            arrMouse(iIndex).MiddleDown = TRUE
                        ElseIf ((raw.mouse.usButtonFlags And 32) = 32) Then
                            arrMouse(iIndex).MiddleDown = FALSE
                        End If

                        ' right button = 4 when down, 8 when released
                        If ((raw.mouse.usButtonFlags And 4) = 4) Then
                            arrMouse(iIndex).RightDown = TRUE
                        ElseIf ((raw.mouse.usButtonFlags And 8) = 8) Then
                            arrMouse(iIndex).RightDown = FALSE
                        End If

                        ' scroll wheel = ???
                        'arrMouse(iIndex).wheel = ???

                        ' DID VALUE CHANGE?
                        If arrMouse(iIndex).UpdateCount = 32767 Then
                            arrMouse(iIndex).UpdateCount = 1
                        Else
                            arrMouse(iIndex).UpdateCount = arrMouse(iIndex).UpdateCount + 1
                        End If

                        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
                        ' COLLECT VALUES FOR THIS MOUSE TO SEND
                        ' IN THE FOLOWING TAB-DELIMITED FORMAT:
                        ' {mouse #}\t{count}\t{dx}\t{dy}\t{wheel}\t{leftDown}\t{middleDown}\t{rightDown}\n
                        sOutput = ""
                        sOutput = sOutput + _Trim$(Str$(iIndex)) + Chr$(9)
                        sOutput = sOutput + _Trim$(Str$(arrMouse(iIndex).UpdateCount)) + Chr$(9)
                        sOutput = sOutput + _Trim$(Str$(arrMouse(iIndex).dx)) + Chr$(9)
                        sOutput = sOutput + _Trim$(Str$(arrMouse(iIndex).dy)) + Chr$(9)
                        sOutput = sOutput + _Trim$(Str$(arrMouse(iIndex).wheel)) + Chr$(9)
                        sOutput = sOutput + _Trim$(Str$(arrMouse(iIndex).LeftDown)) + Chr$(9)
                        sOutput = sOutput + _Trim$(Str$(arrMouse(iIndex).MiddleDown)) + Chr$(9)
                        sOutput = sOutput + _Trim$(Str$(arrMouse(iIndex).RightDown)) + Chr$(13)

                        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
                        ' SEND VALUES FOR THIS MOUSE TO HOST
                        Put #lngConn, , sOutput

                        ' ----------------------------------------------------------------------------------------------------------------------------------------------------------------
                        ' CLEAR MOVEMENT
                        arrMouse(iIndex).dx = 0
                        arrMouse(iIndex).dy = 0

                    End If
                End If

                ' UPDATE mousemessage WITH PLAYING FIELD
                mousemessage = ScreenToString$
                ' ================================================================================================================================================================
                ' END WRITE OUTPUT FILE
                ' ================================================================================================================================================================

                InvalidateRect hwnd, 0, -1
                SendMessage hwnd, WM_PAINT, 0, 0
                MainWndProc = 0

            ElseIf raw.header.dwType = RIM_TYPEKEYBOARD Then
                ' TODO: READ KEYBOARD INPUT

                ' SEE:
                ' https://www.codeproject.com/Articles/17123/Using-Raw-Input-from-C-to-handle-multiple-keyboard
                ' https://forum.powerbasic.com/forum/user-to-user-discussions/powerbasic-for-windows/55985-raw-keyboard-hid-input-discussion
                ' https://hackaday.io/project/5364-cheap-windows-jogkeyboard-controller-for-cncs/log/16843-looking-at-rawinput-for-more-detail

                ' WinAPI Raw Input confusion - For Beginners - GameDev.net
                ' https://www.gamedev.net/forums/topic/700010-winapi-raw-input-confusion/

                'iKeyboardCount = iKeyboardCount + 1 ' # KEYBOARDS ATTACHED
                'strNextID = _Trim$(Str$(rawdevs(x).hDevice))
                'arrKeyboard(iKeyboardCount - 1).ID = strNextID
                ' TODO: READ KEYBOARD AND STORE KEYBOARD STATE
                'arrKeyState(0 To 8, 1 To 512) As Integer ' arrKeyState({device#}, {keyCode}) = TRUE if key {keyCode} on keyboard {device#} is currently held down.

            End If

            MemFree lpb
            MainWndProc = 0
            Exit Function

        Case WM_MOUSEMOVE
            'mousemessage = mousemessage + " X:" + Str$(GET_X_LPARAM(lParam))
            'mousemessage = mousemessage + " Y:" + Str$(GET_Y_LPARAM(lParam))
            'mousemessage = mousemessage + Chr$(0)

            ' SAVE RANGE OF MOUSE COORDINATES
            If GET_X_LPARAM(lParam) < iMinX Then
                iMinX = GET_X_LPARAM(lParam)
                arrMouse(iIndex).dx = -1
            ElseIf GET_X_LPARAM(lParam) > iMaxX Then
                iMaxX = GET_X_LPARAM(lParam)
                arrMouse(iIndex).dx = 1
            Else
                arrMouse(iIndex).dx = 0
            End If

            If GET_Y_LPARAM(lParam) < iMinY Then
                iMinY = GET_Y_LPARAM(lParam)
                arrMouse(iIndex).dy = -1
            ElseIf GET_Y_LPARAM(lParam) > iMaxY Then
                iMaxY = GET_Y_LPARAM(lParam)
                arrMouse(iIndex).dy = 1
            Else
                arrMouse(iIndex).dy = 0
            End If

            ' IDENTIFY WHICH MOUSE IT IS
            strNextID = _Trim$(Str$(raw.header.hDevice))
            iIndex = GetMouseIndex%(strNextID)
            If iIndex >= LBound(arrMouse) Then
                If iIndex <= UBound(arrMouse) Then

                    ' =============================================================================
                    ' UPDATE ABSOLUTE POSITION

                    ' DOESN'T WORK, MOVES ALL OVER THE PLACE:
                    '' METHOD #1: SCALE MOUSE POSITION TO 80X25 POSITION
                    ''iNewX = ( (GET_X_LPARAM(lParam) + 1) * 80) \ 1520
                    'iNewX = ( (GET_X_LPARAM(lParam) + 1) * 80) \ (iMaxX+1)
                    ''iNewY = ( (GET_Y_LPARAM(lParam) + 1) * 25) \ 782
                    'iNewY = ( (GET_Y_LPARAM(lParam) + 1) * 25) \ (iMaxY+1)
                    'arrMouse(iIndex).x = iNewX
                    'arrMouse(iIndex).y = iNewY

                    ' WORKS BUT NOT THAT ACCURATE:
                    ' METHOD #2: INCREMENT/DECREMENT DELTA
                    ' (should we update here too?)

                    'TODO: SAVE SCROLL WHEEL + BUTTONS
                    ' (should we update here too?)
                    'arrMouse(iIndex).wheel =
                    'arrMouse(iIndex).LeftDown =
                    'arrMouse(iIndex).MiddleDown =
                    'arrMouse(iIndex).RightDown =
                End If
            End If

            'DEBUG: SUBSTITUTE _WindowHandle
            InvalidateRect hwnd, 0, -1
            'InvalidateRect _WindowHandle, 0, -1

            'DEBUG: SUBSTITUTE _WindowHandle
            SendMessage hwnd, WM_PAINT, 0, 0
            'SendMessage _WindowHandle, WM_PAINT, 0, 0

            MainWndProc = 0
            Exit Function

        Case WM_PAINT
            'DEBUG: SUBSTITUTE _WindowHandle
            hdc = BeginPaint(hwnd, Offset(ps))
            'hdc = BeginPaint(_WindowHandle, Offset(ps))

            'DEBUG: SUBSTITUTE _WindowHandle
            GetClientRect hwnd, Offset(rc)
            'GetClientRect _WindowHandle, Offset(rc)

            DrawText hdc, Offset(mousemessage), Len(mousemessage), Offset(rc), DT_CENTER
            OffsetRect Offset(rc), 0, 200

            '' PRINT LIST OF RawInput DEVICES:
            'DrawText hdc, Offset(rawinputdevices), Len(rawinputdevices), Offset(rc), DT_CENTER

            'DEBUG: SUBSTITUTE _WindowHandle
            EndPaint hwnd, Offset(ps)
            'EndPaint _WindowHandle, Offset(ps)

            MainWndProc = 0
            Exit Function

        Case Else
            'DEBUG: SUBSTITUTE _WindowHandle
            MainWndProc = DefWindowProc(hwnd, nMsg, wParam, lParam)
            'MainWndProc = DefWindowProc(_WindowHandle, nMsg, wParam, lParam)
    End Select

    If _KeyDown(27) Then End

End Function ' MainWndProc

' /////////////////////////////////////////////////////////////////////////////
' Initializes raw input stuff

Sub InitRawInput ()
    Dim As RAWINPUTDEVICE Rid(0 To 49)
    Dim As Unsigned Long nDevices
    Dim As RAWINPUTDEVICELIST RawInputDeviceList
    Dim As MEM pRawInputDeviceList
    ReDim As RAWINPUTDEVICELIST rawdevs(-1)
    Dim As Unsigned Long x
    Dim strNextID As String
    'dim lngNextID as long

    If GetRawInputDeviceList(0, Offset(nDevices), Len(RawInputDeviceList)) <> 0 Then
        Exit Sub
    End If

    pRawInputDeviceList = MemNew(Len(RawInputDeviceList) * nDevices)
    GetRawInputDeviceList pRawInputDeviceList.OFFSET, Offset(nDevices), Len(RawInputDeviceList)

    ' This small block of commented code proves that we've got the device list
    ReDim As RAWINPUTDEVICELIST rawdevs(0 To nDevices - 1)
    MemGet pRawInputDeviceList, pRawInputDeviceList.OFFSET, rawdevs()

    ' GET MOUSE / KEYBOARD INFO
    iMouseCount = 0
    iKeyboardCount = 0

    rawinputdevices = "Number of raw input devices:" + Str$(nDevices) + Chr$(13)

    For x = 0 To UBound(rawdevs)
        rawinputdevices = rawinputdevices + Str$(rawdevs(x).hDevice) + ":" + Str$(rawdevs(x).dwType) + Chr$(13)

        ' RAWINPUTHEADER (winuser.h) - Win32 apps | Microsoft Learn
        ' https://learn.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-rawinputheader
        ' dwType
        ' Type: DWORD
        ' The type of raw input. It can be one of the following values:
        ' Constant           Value   Meaning
        ' RIM_TYPEMOUSE      0       Raw input comes from the mouse.
        ' RIM_TYPEKEYBOARD   1       Raw input comes from the keyboard.
        ' RIM_TYPEHID        2       Raw input comes from some device that is not a keyboard or a mouse.

        ' WHAT TYPE OF DEVICE IS IT?
        'If rawdevs(x).dwType = 0 Then
        If rawdevs(x).dwType = RIM_TYPEMOUSE Then
            iMouseCount = iMouseCount + 1
            strNextID = _Trim$(Str$(rawdevs(x).hDevice))
            'lngNextID = Val(strNextID)
            'arrMouse(iMouseCount-1).ID = lngNextID
            arrMouse(iMouseCount - 1).ID = strNextID
            arrMouse(iMouseCount - 1).UpdateCount = 0
            'TODO: SAVE_MOUSE_INFO

        ElseIf rawdevs(x).dwType = RIM_TYPEKEYBOARD Then
            iKeyboardCount = iKeyboardCount + 1 ' # KEYBOARDS ATTACHED
            strNextID = _Trim$(Str$(rawdevs(x).hDevice))
            arrKeyboard(iKeyboardCount - 1).ID = strNextID
            arrKeyboard(iKeyboardCount - 1).UpdateCount = 0
            ' TODO: READ KEYBOARD AND STORE KEYBOARD STATE

            'arrKeyState(0 To 8, 1 To 512) As Integer ' arrKeyState({device#}, {keyCode}) = TRUE if key {keyCode} on keyboard {device#} is currently held down.
        End If

    Next x
    rawinputdevices = rawinputdevices + Chr$(0)

    MemFree pRawInputDeviceList

    Rid(0).usUsagePage = &H01
    Rid(0).usUsage = &H02
    Rid(0).dwFlags = 0

    'DEBUG: SUBSTITUTE _WindowHandle
    Rid(0).hwndTarget = 0
    'Rid(0).hwndTarget = _WindowHandle

    If RegisterRawInputDevices(Offset(Rid()), 1, Len(Rid(0))) = 0 Then
        mousemessage = "RawInput init failed" + Chr$(0)
    End If
End Sub ' InitRawInput

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' END RAW INPUT FUNCTIONS
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' BEGIN WINDOW FUNCTIONS
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' /////////////////////////////////////////////////////////////////////////////
' hWnd = handle to window to set opacity for
' Level = 0 TO 255, 0=totally invisible, 128=transparent, 255=100% solid

Sub SetWindowOpacity (hWnd As _Offset, Level As _Unsigned _Byte)
    Const cIndex = -20
    Const LWA_ALPHA = &H2
    Const WS_EX_LAYERED = &H80000

    Dim lngMsg As Long
    Dim lngValue As Long

    'Function GetWindowLong& Alias "GetWindowLongA" (ByVal hwnd As _Offset, Byval nIndex As Long)
    lngMsg = GetWindowLong(hWnd, cIndex)
    lngMsg = lngMsg Or WS_EX_LAYERED

    'Function SetWindowLong& Alias "SetWindowLongA" (ByVal hwnd As _Offset, Byval nIndex As Long, Byval dwNewLong As Long)
    lngValue = SetWindowLong(hWnd, cIndex, lngMsg)

    'Function SetLayeredWindowAttributes& (ByVal hwnd As _Offset, Byval crKey As Long, Byval bAlpha As _Unsigned _Byte, Byval dwFlags As Long)
    lngValue = SetLayeredWindowAttributes(hWnd, 0, Level, LWA_ALPHA)
End Sub ' SetWindowOpacity

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' END WINDOW FUNCTIONS
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' BEGIN MOUSE TEST FUNCTIONS #1
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' /////////////////////////////////////////////////////////////////////////////
' Initialize mouse test stuff

'TODO: SAVE_MOUSE_INFO

Sub InitMouseTest
    Dim iIndex As Integer
    Dim iLoop As Integer

    ' FOR NOW ONLY SUPPORT UPTO 8 MICE
    If (iMouseCount > 8) Then iMouseCount = 8

    ' INITIALIZE CURSORS, MOUSE STATE, ETC.
    Restore CData
    iIndex = LBound(arrMouse) - 1
    For iLoop = 1 To iMouseCount
        iIndex = iIndex + 1
        Read arrMouse(iIndex).c
        ' INITIALIZED BELOW: arrMouse(iIndex).x = 0
        ' INITIALIZED BELOW: arrMouse(iIndex).y = 0
        ' INITIALIZED BELOW: arrMouse(iIndex).wheel = 127
        arrMouse(iIndex).LeftDown = FALSE
        arrMouse(iIndex).MiddleDown = FALSE
        arrMouse(iIndex).RightDown = FALSE
        arrMouse(iIndex).LeftCount = 0
        arrMouse(iIndex).MiddleCount = 0
        arrMouse(iIndex).RightCount = 0
        arrMouse(iIndex).UpdateCount = 1
    Next iLoop

    ' INITIALIZE X COORDINATES
    Restore XData
    iIndex = LBound(arrMouse) - 1
    For iLoop = 1 To iMouseCount
        iIndex = iIndex + 1
        Read arrMouse(iIndex).x
    Next iLoop

    ' INITIALIZE Y COORDINATES
    Restore YData
    iIndex = LBound(arrMouse) - 1
    For iLoop = 1 To iMouseCount
        iIndex = iIndex + 1
        Read arrMouse(iIndex).y
    Next iLoop

    ' INITIALIZE SCROLL WHEEL
    Restore WData
    iIndex = LBound(arrMouse) - 1
    For iLoop = 1 To iMouseCount
        iIndex = iIndex + 1
        Read arrMouse(iIndex).wheel
    Next iLoop

End Sub ' InitMouseTest

' /////////////////////////////////////////////////////////////////////////////
' Finds position in array arrMouse where .ID = MouseID

Function GetMouseIndex% (MouseID As String)
    Dim iLoop As Integer
    Dim iIndex%
    iIndex% = LBound(arrMouse) - 1
    For iLoop = LBound(arrMouse) To UBound(arrMouse)
        If arrMouse(iLoop).ID = MouseID Then
            iIndex% = iLoop
            Exit For
        Else
            ' not it
        End If
    Next iLoop
    GetMouseIndex% = iIndex%
End Function ' GetMouseIndex%

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' END MOUSE TEST FUNCTIONS #1
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' BEGIN TEST OUTPUT FUNCTIONS FOR API CONTROLLED UI
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' /////////////////////////////////////////////////////////////////////////////
' Clears global array arrScreen

Sub ClearText
    Dim iColNum As Integer
    Dim iRowNum As Integer
    For iColNum = LBound(arrScreen, 1) To UBound(arrScreen, 1)
        For iRowNum = LBound(arrScreen, 2) To UBound(arrScreen, 2)
            arrScreen(iColNum, iRowNum) = " "
        Next iRowNum
    Next iColNum
End Sub ' ClearText

' /////////////////////////////////////////////////////////////////////////////
' Plots string MyString to position (iX, iY) in global array arrScreen.

Sub WriteText (iRow As Integer, iColumn As Integer, MyString As String)
    Dim iPos As Integer
    Dim iLoop As Integer
    If iColumn > 0 And iColumn < 81 Then
        If iRow > 0 And iRow < 26 Then
            For iLoop = 1 To Len(MyString)
                iPos = iColumn + (iLoop - 1)
                If iPos < 81 Then
                    arrScreen(iPos, iRow) = Mid$(MyString, iLoop, 1)
                Else
                    Exit For
                End If
            Next iLoop
        End If
    End If
End Sub ' WriteText

' /////////////////////////////////////////////////////////////////////////////
' Converts global array arrScreen to a string.

Function ScreenToString$
    Dim sResult As String
    Dim iColNum As Integer
    Dim iRowNum As Integer
    sResult = ""
    For iRowNum = LBound(arrScreen, 2) To UBound(arrScreen, 2)
        For iColNum = LBound(arrScreen, 1) To UBound(arrScreen, 1)
            sResult = sResult + arrScreen(iColNum, iRowNum)
        Next iColNum
        sResult = sResult + Chr$(13)
    Next iRowNum
    ScreenToString$ = sResult
End Function ' ScreenToString$

' /////////////////////////////////////////////////////////////////////////////
' based on code from:
' Qbasic Programs - Download free bas source code
' http://www.thedubber.altervista.org/qbsrc.htm

Sub DrawTextLine (y%, x%, y2%, x2%, c$)
    Dim i%
    Dim steep%
    Dim e%
    Dim sx%
    Dim dx%
    Dim sy%
    Dim dy%

    i% = 0: steep% = 0: e% = 0
    If (x2% - x%) > 0 Then sx% = 1: Else sx% = -1
    dx% = Abs(x2% - x%)
    If (y2% - y%) > 0 Then sy% = 1: Else sy% = -1
    dy% = Abs(y2% - y%)
    If (dy% > dx%) Then
        steep% = 1
        Swap x%, y%
        Swap dx%, dy%
        Swap sx%, sy%
    End If
    e% = 2 * dy% - dx%
    For i% = 0 To dx% - 1
        If steep% = 1 Then
            ''PSET (y%, x%), c%:
            'Locate y%, x% : Print c$;
            WriteText y%, x%, c$
        Else
            ''PSET (x%, y%), c%
            'Locate x%, y% : Print c$;
            WriteText x%, y%, c$
        End If

        While e% >= 0
            y% = y% + sy%: e% = e% - 2 * dx%
        Wend
        x% = x% + sx%: e% = e% + 2 * dy%
    Next
    ''PSET (x2%, y2%), c%
    'Locate x2%, y2% : Print c$;
    WriteText x2%, y2%, c$
End Sub ' DrawTextLine

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' END TEST OUTPUT FUNCTIONS FOR API CONTROLLED UI
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' BEGIN MOUSE FUNCTIONS TO COME
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' /////////////////////////////////////////////////////////////////////////////
' Returns a count of # of RawInput mouse devices connected to the system

' *****************************************************************************
' TODO: GET COUNT FROM RawInput API
' For now, hardcoded to 1 until we figure out how to do this.
' *****************************************************************************
Function GetRawMouseCount% ()
    GetRawMouseCount% = 1
End Function ' GetRawMouseCount%

' /////////////////////////////////////////////////////////////////////////////
' Gets ID of each RawInput mouse device connected to the system (for now upto 8)

' Returns the IDs in an array of LONG <- may change depending on whether
' we save each the device handle for each mouse or the index

' If no mouse found, the ID will just be 0 <- or whatever value we decide as default/none

' *****************************************************************************
' TODO: GET THIS FROM RawInput API
' For now, hardcoded arrRawMouseID(1) to 1, and the rest 0, until we figure out how to do this.
' *****************************************************************************
'Sub GetRawMouseIDs (arrRawMouseID( 8) As Integer)
Sub GetRawMouseIDs ()
    Dim iLoop As Integer

    ' CLEAR OUT IDs
    For iLoop = 1 To 8
        ''arrRawMouseID(iLoop) = 0
        'arrMouse(iLoop).ID = 0
        arrMouse(iLoop).ID = ""
    Next iLoop

    ' GET IDs
    'TODO: get this from RawInput API
    ''arrRawMouseID(1) = 1 ' for now just fudge it!
    'arrMouse(0).ID = 1 ' for now just fudge it!
End Sub ' GetRawMouseIDs

' /////////////////////////////////////////////////////////////////////////////
' Read mouse using RawInput API

' Gets input from mouse, MouseID% = which mouse

' NOTE: click events (mouse up/mouse down) are handled by the calling sub,
'       this routine just sends back
'       TRUE if the given button is currently down or FALSE if it is up.

' Parameters (input only):
' MouseID% = which mouse to return input for
' wheelMin% = minimum value to allow wheelValue% to be decremented to
' wheelMax% = maximum value to allow wheelValue% to be incremened to

' Parameters (values returned):
' x% = mouse x position
' y% = mouse y position
' leftButton% = current state of left mouse button (up or down)
' middleButton% = current state of middle mouse button / scroll wheel button (up or down)
' rightButton% = current state of right mouse button (up or down)
' wheelValue% = value of mouse scroll wheel (passed in and incremented/decremented by 1 if wheel move detected)

Sub ReadRawMouse (MouseID%, x%, y%, leftButton%, middleButton%, rightButton%, wheelValue%, wheelMin%, wheelMax%)
    Dim scrollAmount%
    Dim dx%
    Dim dy%

    ' =============================================================================
    ' BEGIN READ MOUSE THE NEW RawInput WAY:

    ' read scroll wheel
    'TODO: get this from RawInput API

    ' determine mouse x position
    'TODO: get this from RawInput API
    dx% = 0 ' = getMouseDx(MouseID%)
    x% = x% + dx% ' adjust mouse value by dx

    ' determine mouse y position
    'TODO: get this from RawInput API
    dy% = 0 ' = getMouseDy(MouseID%)
    y% = y% + dy% ' adjust mouse value by dx

    ' read mouse buttons
    'TODO: get this from RawInput API
    leftButton% = FALSE
    middleButton% = FALSE
    rightButton% = FALSE

    ' END READ MOUSE THE NEW RawInput WAY:
    ' =============================================================================

    ' =============================================================================
    ' BEGIN READ MOUSE THE OLD QB64 WAY:
    '
    '' read scroll wheel
    'WHILE _MOUSEINPUT ' get latest mouse information
    '    scrollAmount% = _MOUSEWHEEL ' (Returns -1 when scrolling up and 1 when scrolling down with 0 indicating no movement since last read.)
    '    IF (scrollAmount% = -1) AND (wheelValue% > wheelMin%) THEN
    '        wheelValue% = wheelValue% + scrollAmount%
    '    ELSEIF (scrollAmount% = 1) AND (wheelValue% < wheelMax%) THEN
    '        wheelValue% = wheelValue% + scrollAmount%
    '    END IF
    'WEND
    '
    '' determine mouse x position
    'x% = _MOUSEX
    '
    '' determine mouse y position
    'y% = _dy
    '
    '' read mouse buttons
    'leftButton% = _MOUSEBUTTON(1)
    'middleButton% = _MOUSEBUTTON(3)
    'rightButton% = _MOUSEBUTTON(2)
    '
    ' END READ MOUSE THE OLD QB64 WAY:
    ' =============================================================================

End Sub ' ReadRawMouse

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' END MOUSE FUNCTIONS TO COME
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' BEGIN ERROR HANDLING HELPER FUNCTIONS
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Sub ErrorClear
    m_sError = ""
    m_sIncludeError = ""
End Sub ' ErrorClear
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' END ERROR HANDLING HELPER FUNCTIONS
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' BEGIN FILE FUNCTIONS
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' /////////////////////////////////////////////////////////////////////////////
' QB64 Phoenix Edition › QB64 Rising › Code and Stuff › Help Me!
' Using shell to delete a file
' https://qb64phoenix.com/forum/showthread.php?tid=2618&pid=24683#pid24683
' a740g
' #5
' 04-24-2024, 06:05 AM
'
' There are no commands to directly make copies or backup of files.
' But you could write one with a few lines of code like:
'
' Copies src to dst
' Set overwite to true if dst should be overwritten if present
Sub CopyFile (src As String, dst As String, overwrite As _Byte)
    If _FileExists(src) Then
        If Not _FileExists(dst) Or (_FileExists(dst) And overwrite) Then
            _WriteFile dst, _ReadFile$(src)
        End If
    End If
End Sub ' CopyFile

' /////////////////////////////////////////////////////////////////////////////
' Convert a value to string and trim it (because normal Str$ adds spaces)

Function cstr$ (myValue)
    'cstr$ = LTRIM$(RTRIM$(STR$(myValue)))
    cstr$ = _Trim$(Str$(myValue))
End Function ' cstr$

' /////////////////////////////////////////////////////////////////////////////
' QB64 Phoenix Edition › QB64 Rising › Code and Stuff › Help Me!
' Using shell to delete a file
' https://qb64phoenix.com/forum/showthread.php?tid=2618

Sub DeleteFile (sFile As String)
    If _FileExists(sFile) Then
        'Shell "DELETE " + sFile
        'Shell "del " + sFile
        Kill sFile
    End If
End Sub ' DeleteFile

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

Function FileExt$ (sFile As String)
    Dim iPos As Integer
    iPos = _InStrRev(sFile, ".")
    If iPos > 0 Then
        If Len(sFile) > 1 Then
            If iPos > 1 Then
                FileExt$ = Right$(sFile, Len(sFile) - iPos)
            Else
                ' dot is first character, return everything after it
                FileExt$ = Right$(sFile, Len(sFile) - 1)
            End If
        Else
            ' file only has one character, the dot, the file extension is blank
            FileExt$ = ""
        End If
    Else
        ' no dot found, the file extension is blank
        FileExt$ = ""
    End If
End Function ' FileExt$

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

Function NameOnly$ (sFile As String, sSlash As String)
    Dim iPos As Integer
    'sFile = Replace$(sFile, "/", "\")

    iPos = _InStrRev(sFile, sSlash)
    If iPos > 0 Then
        If Len(sFile) > 1 Then
            If iPos > 1 Then
                NameOnly$ = Right$(sFile, Len(sFile) - iPos)
            Else
                ' slash is first character, return everything after it
                NameOnly$ = Right$(sFile, Len(sFile) - 1)
            End If
        Else
            ' file only has one character, the slash, name is blank
            NameOnly$ = ""
        End If
    Else
        ' slash not found, return the entire thing
        NameOnly$ = sFile
    End If
End Function ' NameOnly$

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

Function NoExt$ (sFile As String)
    Dim iPos As Integer
    iPos = _InStrRev(sFile, ".")
    If iPos > 0 Then
        If Len(sFile) > 1 Then
            If iPos > 1 Then
                NoExt$ = Left$(sFile, iPos - 1)
            Else
                ' dot is first character, removing it returns blank!
                ' our version will just return the name unchanged
                ' but you can return blank if you prefer
                NoExt$ = sFile
            End If
        Else
            ' file only has one character, the dot, removing it returns blank!
            ' our version will just return the name unchanged
            ' but you can return blank if you prefer
            NoExt$ = sFile
        End If
    Else
        ' no dot found
        ' return the name unchanged
        NoExt$ = sFile
    End If
End Function ' NoExt$

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

Function PathOnly$ (sFile As String, sSlash As String)
    Dim iPos As Integer
    'sFile = Replace$(sFile, "/", "\")

    iPos = _InStrRev(sFile, sSlash)
    If iPos > 0 Then
        If Len(sFile) > 1 Then
            If iPos > 1 Then
                PathOnly$ = Left$(sFile, iPos)
            Else
                ' slash is first character, so not much of a path, return blank
                PathOnly$ = ""
            End If
        Else
            ' file only has one character, the slash, name is blank
            PathOnly$ = ""
        End If
    Else
        ' slash not found, so not a path, return blank
        PathOnly$ = ""
    End If
End Function ' PathOnly$

' /////////////////////////////////////////////////////////////////////////////
' Writes sText to file sFileName.
' If bAppend=TRUE appends to file, else overwrites it.

' Returns blank if successful else returns error message.

' Example:
' ProgramPath$ = Left$(Command$(0), _InStrRev(Command$(0), "\"))
' ProgramName$: m_ProgramName$ = Mid$(Command$(0), _InStrRev(Command$(0), "\") + 1)
' sFileName = ProgramPath$ + ProgramName$ + ".OUT.txt"
' sText = "This is a test." + chr$(13) + "Here is line 2." + chr$(13) + "End."
' sError = PrintFile$(sFileName, sText, FALSE)

Function PrintFile$ (sFileName As String, sText As String, bAppend As Integer)
    Dim sError As String: sError = ""

    If (bAppend = TRUE) Then
        If _FileExists(sFileName) Then
            Open sFileName For Append As #1 ' opens an existing file for appending
        Else
            sError = "Error in PrintFile$ : File not found. Cannot append."
        End If
    Else
        Open sFileName For Output As #1 ' opens and clears an existing file or creates new empty file
    End If

    If Len(sError) = 0 Then
        ' NOTE: WRITE places text in quotes in the file
        'WRITE #1, x, y, z$
        'WRITE #1, sText

        ' PRINT does not put text inside quotes
        Print #1, sText

        Close #1
    End If

    PrintFile$ = sError
End Function ' PrintFile$

' /////////////////////////////////////////////////////////////////////////////
' Fastest way is always to just read the whole life at once and then parse it.

Function ReadTextFile$ (sFileName As String, sDefault As String)
    Dim x$
    If _FileExists(sFileName) Then
        Open sFileName For Binary As #1
        x$ = Space$(LOF(1))
        Get #1, 1, x$
        Close #1
        ReadTextFile$ = x$
    Else
        ReadTextFile$ = sDefault
    End If
End Function ' ReadTextFile$

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' END FILE FUNCTIONS
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' BEGIN GENERAL PURPOSE FUNCTIONS
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' /////////////////////////////////////////////////////////////////////////////
' FOR BITWISE OPERATIONS

Function HasBit% (iByte As Integer, iBit As Integer)
    ''TODO: precalculate
    'dim shared m_arrBitValue(1 To 8) As Integer
    'dim iLoop as Integer
    'For iLoop = 0 To 7
    '   m_arrBitValue(iLoop + 1) = 2 ^ iLoop
    'Next iLoop
    'HasBit% = ((iByte And m_arrBitValue(iBit)) = m_arrBitValue(iBit))
    Dim iBitValue As Integer
    iBitValue = 2 ^ (iBit - 1)
    HasBit% = ((iByte And iBitValue) = iBitValue)
End Function ' HasBit%

' /////////////////////////////////////////////////////////////////////////////
' Returns TRUE if value OriginalString$ is numeric.

' Re: Does a Is Number function exist in QB64?
' https://www.qb64.org/forum/index.php?topic=896.15

' Version 2 by madscijr
' Returns TRUE (-1) if string is an integer, FALSE (0) if not

' Version 1 by MWheatley
' Reply #18 on: January 01, 2019, 11:24:30 AM
' returns 1 if string is an integer, 0 if not

Function IsNumber% (OriginalString$)
    Dim bResult%: bResult% = FALSE
    Dim iLoop%
    Dim TestString$
    'Dim bNegative%
    Dim iDecimalCount%
    Dim sNextChar$

    'THEY SHOULD TRIM OUTSIDE THE FUNCTION!
    'TestString$ = _TRIM$(OriginalString$)

    If Len(OriginalString$) > 0 Then
        TestString$ = ""
        If Left$(OriginalString$, 1) = "+" Then
            TestString$ = Right$(OriginalString$, Len(OriginalString$) - 1)
            'bNegative% = FALSE
        ElseIf Left$(OriginalString$, 1) = "-" Then
            TestString$ = Right$(OriginalString$, Len(OriginalString$) - 1)
            'bNegative% = TRUE
        Else
            TestString$ = OriginalString$
            'bNegative% = FALSE
        End If
        If Len(TestString$) > 0 Then
            bResult% = TRUE
            iDecimalCount% = 0
            For iLoop% = 1 To Len(TestString$)
                sNextChar$ = Mid$(TestString$, iLoop%, 1)
                If sNextChar$ = "." Then
                    iDecimalCount% = iDecimalCount% + 1
                    If iDecimalCount% > 1 Then
                        ' TOO MANY DECIMAL POINTS, INVALID!
                        bResult% = FALSE
                        Exit For
                    End If
                ElseIf Asc(sNextChar$) < 48 Or Asc(sNextChar$) > 57 Then
                    ' NOT A NUMERAL OR A DECIMAL, INVALID!
                    bResult% = FALSE
                    Exit For
                End If
            Next iLoop%
        End If
    End If
    IsNumber% = bResult%
End Function ' IsNumber%

' /////////////////////////////////////////////////////////////////////////////
' Split and join strings
' https://www.qb64.org/forum/index.php?topic=1073.0

'Combine all elements of in$() into a single string with delimiter$ separating the elements.

Function join$ (in$(), delimiter$)
    Dim result$
    Dim iLoop%
    result$ = in$(LBound(in$))
    For iLoop% = LBound(in$) + 1 To UBound(in$)
        result$ = result$ + delimiter$ + in$(iLoop%)
    Next iLoop%
    join$ = result$
End Function ' join$

' /////////////////////////////////////////////////////////////////////////////
' Split and join strings
' https://www.qb64.org/forum/index.php?topic=1073.0
'
' FROM luke, QB64 Developer
' Date: February 15, 2019, 04:11:07 AM
'
' Given a string of words separated by spaces (or any other character),
' splits it into an array of the words. I've no doubt many people have
' written a version of this over the years and no doubt there's a million
' ways to do it, but I thought I'd put mine here so we have at least one
' version. There's also a join function that does the opposite
' array -> single string.
'
' Code is hopefully reasonably self explanatory with comments and a little demo.
' Note, this is akin to Python/JavaScript split/join, PHP explode/implode.

'Split in$ into pieces, chopping at every occurrence of delimiter$. Multiple consecutive occurrences
'of delimiter$ are treated as a single instance. The chopped pieces are stored in result$().
'
'delimiter$ must be one character long.
'result$() must have been REDIMmed previously.

' Modified to handle multi-character delimiters

Sub split (in$, delimiter$, result$())
    Dim start As Integer
    Dim finish As Integer
    Dim iDelimLen As Integer
    ReDim result$(-1)

    iDelimLen = Len(delimiter$)

    start = 1
    Do
        'While Mid$(in$, start, 1) = delimiter$
        While Mid$(in$, start, iDelimLen) = delimiter$
            'start = start + 1
            start = start + iDelimLen
            If start > Len(in$) Then
                Exit Sub
            End If
        Wend
        finish = InStr(start, in$, delimiter$)
        If finish = 0 Then
            finish = Len(in$) + 1
        End If

        ReDim _Preserve result$(0 To UBound(result$) + 1)

        result$(UBound(result$)) = Mid$(in$, start, finish - start)
        start = finish + 1
    Loop While start <= Len(in$)
End Sub ' split

' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
' END GENERAL PURPOSE FUNCTIONS
' ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

' ################################################################################################################################################################
' #REFERENCE

' =============================================================================
' SOME USEFUL STUFF FOR REFERENCE:

' Type Name               Type suffix symbol   Minimum value                  Maximum value                Size in Bytes
' ---------------------   ------------------   ----------------------------   --------------------------   -------------
' _BIT                    `                    -1                             0                            1/8
' _BIT * n                `n                   -128                           127                          n/8
' _UNSIGNED _BIT          ~`                   0                              1                            1/8
' _BYTE                   %%                   -128                           127                          1
' _UNSIGNED _BYTE         ~%%                  0                              255                          1
' INTEGER                 %                    -32,768                        32,767                       2
' _UNSIGNED INTEGER       ~%                   0                              65,535                       2
' LONG                    &                    -2,147,483,648                 2,147,483,647                4
' _UNSIGNED LONG          ~&                   0                              4,294,967,295                4
' _INTEGER64              &&                   -9,223,372,036,854,775,808     9,223,372,036,854,775,807    8
' _UNSIGNED _INTEGER64    ~&&                  0                              18,446,744,073,709,551,615   8
' SINGLE                  ! or none            -2.802597E-45                  +3.402823E+38                4
' DOUBLE                  #                    -4.490656458412465E-324        +1.797693134862310E+308      8
' _FLOAT                  ##                   -1.18E-4932                    +1.18E+4932                  32(10 used)
' _OFFSET                 %&                   -9,223,372,036,854,775,808     9,223,372,036,854,775,807    Use LEN
' _UNSIGNED _OFFSET       ~%&                  0                              18,446,744,073,709,551,615   Use LEN
' _MEM                    none                 combined memory variable type  N/A                          Use LEN

' div: int1% = num1% \ den1%
' mod: rem1% = num1% MOD den1%

' @END



Attached Files
.zip   readmicesub35.zip (Size: 46.04 KB / Downloads: 26)
Print this item