Author Topic: good way to implement an array of stacks/linked lists (for 2D tile-based game)?  (Read 5154 times)

0 Members and 1 Guest are viewing this topic.

Offline madscijr

  • Seasoned Forum Regular
  • Posts: 295
    • View Profile
I'm wondering how might we handle storing a variable number of objects at a certain (x,y) location in a tile-based game where the terrain is a simple 2-D array?
We could add a 3rd "Z" dimension, which would allow us to stack items, but most of the space would probably be unused, and there the number of objects a square could hold would be limited by the size of the third dimension.
I would want to preserve the "stacking order" of the objects on a given square, and be able to quickly read through the list of items, given an x,y coordinate, so I feel like a 2-D array of stacks would work or even linked lists.
Can anyone recommend a relatively efficient way to accomplish this QB or QB64, that is not too complicated?

Offline SpriggsySpriggs

  • Forum Resident
  • Posts: 1145
  • Larger than life
    • View Profile
    • GitHub
This is above my knowledge but I know @STxAxTIC has a library for linked lists that may be of some assistance to you. I don't know where the forum post for it is but maybe he can jump in and assist.
Shuwatch!

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Wouldn't the easiest way to do something like this be just:

DIM Objects(x, y) AS STRING * 10 'for a maximum of 10 items per tile

Object(10,10) = CHR$(1) 'Item 1 is on the bottom of the stack
Object(10,10) = Object(10,10) + CHR$(23) 'Item 23 is on top of it
Object(10,10) = Object(10,10) + CHR$(107) 'Item 107 is above that one
Object(10,10) = Object(10,10) + CHR$(114) 'And item 114 is rendered last and is the top of the stack
' And the next 6 characters are all blank

Putting them into your tile would be that simple, and getting the info back out would just be:

FOR x = 0 to x_tile_limit
    FOR y = 0 to y_tile_limit
        FOR z = 1 to 10
            IF ASC(Object(x, y), z) <> 0 THEN
               'Do whatever you do with that object
            END IF
NEXT z, y, x
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline Petr

  • Forum Resident
  • Posts: 1720
  • The best code is the DNA of the hops.
    • View Profile
Easiest it is using TYPE. Just create usertype field structure. Something as this:

Code: QB64: [Select]
  1.  
  2.  
  3. TYPE UserField
  4.     texture AS LONG
  5.     Dynamic_Value AS LONG
  6.  
  7. TYPE Array2D
  8.     Grid AS UserField
  9.     X_POS AS LONG
  10.     Y_POS AS LONG
  11.  
  12. REDIM UserF(100, 100) AS Array2D
  13.  
  14. FOR x = 1 TO 100
  15.     FOR y = 1 TO 100
  16.  
  17.         UserF(x, y).X_POS = x 'fill grid X and Y positions
  18.         UserF(x, y).Y_POS = y
  19.  
  20.         UserF(x, y).Grid.texture = RND * 1000 '_LOADIMAGE....
  21.         UserF(x, y).Grid.Dynamic_Value = RND * 75 '_SNDOPEN.....
  22.  
  23.     NEXT y
  24.  
  25.  

Offline madscijr

  • Seasoned Forum Regular
  • Posts: 295
    • View Profile
Thanks everyone, I will give those various methods a look and let you know how it went!

Offline madscijr

  • Seasoned Forum Regular
  • Posts: 295
    • View Profile
Wouldn't the easiest way to do something like this be just:
DIM Objects(x, y) AS STRING * 10 'for a maximum of 10 items per tile
...
' And the next 6 characters are all blank

Thanks for your suggestion.
What is the DIM AS STRING * 10 all about?
Does that mean the string is always 10 characters long, and any unused characters are automatically filled in with space?

So this is setting aside memory for (x_tile_limit, y_tile_limit, 10), and (x_tile_limit * y_tile_limit * 10) bytes are used even if there is nothing on any tile.
Wouldn't this be the same as using a 3-dimensional array DIM Objects (x_tile_limit, y_tile_limit, 10) ?

We could just do
DIM Objects(x_tile_limit, y_tile_limit) AS STRING

where each tile is limited to the size of the string,

and then to get the number of objects on the tile just do
iNumObjects = LEN(Objects(xPos, yPos)),

to POP, do
MyObject$ = RIGHT$(Objects(xPos, yPos), 1)
Objects(xPos, yPos) = LEFT$(Objects(xPos, yPos), LEN(Objects(xPos, yPos))-1)

and to PUSH, do
Objects(xPos, yPos) = Objects(xPos, yPos) + NewObject$

It's the simplest approach.

Thanks again


Offline madscijr

  • Seasoned Forum Regular
  • Posts: 295
    • View Profile
Easiest it is using TYPE. Just create usertype field structure.

Thanks for your reply. I don't see how this is a stack, where each cell or tile can hold multiple items (0 upto whatever limit)?

Offline madscijr

  • Seasoned Forum Regular
  • Posts: 295
    • View Profile
The simple string method works - see method #3 in the code below. If anyone can think of a way to implement a 2-D array of stacks that performs better, let me know.

Code: QB64: [Select]
  1. ' OPTIONS
  2.  
  3. ' GLOBAL DECLARATIONS a$=string, i%=integer, L&=long, s!=single, d#=double
  4. Const FALSE = 0
  5. Const TRUE = Not FALSE
  6.  
  7. ' FOR Petr's suggestion #2:
  8. Type UserField
  9.     texture As Long
  10.     Dynamic_Value As Long
  11. Type Array2D
  12.     Grid As UserField
  13.     X_POS As Long
  14.     Y_POS As Long
  15.  
  16. ' INITIALIZE
  17. Dim Shared ProgramPath$: ProgramPath$ = Left$(Command$(0), _InStrRev(Command$(0), "\"))
  18. Dim Shared ProgramName$: ProgramName$ = Mid$(Command$(0), _InStrRev(Command$(0), "\") + 1)
  19.  
  20. ' RUN THE MAIN PROGRAM
  21. main
  22.  
  23. ' FINISH
  24. System ' return control to the operating system
  25. Print ProgramName$ + " finished."
  26.  
  27. ' /////////////////////////////////////////////////////////////////////////////
  28.  
  29. Sub main
  30.     Dim in$: in$ = ""
  31.     Do
  32.         Cls
  33.         Print ProgramName$
  34.         Print
  35.         Print "Test different ways to implement an array of stacks"
  36.         Print
  37.         Print "1. Method #1 = fixed length string"
  38.         Print
  39.         Print "2. Method #2 = user defined type"
  40.         Print
  41.         Print "3. Method #3 = simple string"
  42.         Print
  43.         Print "4. (TBD)"
  44.         Print
  45.         Print "What to do ('q' to exit)"
  46.  
  47.         Input in$: in$ = LCase$(Left$(in$, 1))
  48.  
  49.         If in$ = "1" Then
  50.             ArrayOfStacks1
  51.             _KeyClear: '_DELAY 1
  52.  
  53.         ElseIf in$ = "2" Then
  54.             ArrayOfStacks2
  55.             _KeyClear: '_DELAY 1
  56.  
  57.         ElseIf in$ = "3" Then
  58.             ArrayOfStacks3
  59.             _KeyClear: '_DELAY 1
  60.  
  61.         ElseIf in$ = "4" Then
  62.             _KeyClear: '_DELAY 1
  63.             Print "Under construction"
  64.  
  65.         End If
  66.     Loop Until in$ = "q"
  67. End Sub ' main
  68.  
  69. ' /////////////////////////////////////////////////////////////////////////////
  70. ' Re: good way to implement an array of stacks/linked lists (for 2D tile-based game)?
  71. ' https://www.qb64.org/forum/index.php?topic=4008.0
  72.  
  73. ' SMcNeill
  74. ' « Reply #2 on: Today at 03:28:41 PM »
  75.  
  76. ' Wouldn't the easiest way to do something like this be just:
  77.  
  78. Sub ArrayOfStacks1
  79.     Const x_tile_limit = 20
  80.     Const y_tile_limit = 20
  81.     Const z_stack_limit = 5
  82.  
  83.     'REDIM Objects(x_tile_limit, y_tile_limit) AS STRING * 10 'for a maximum of 10 items per tile
  84.     ReDim Objects(x_tile_limit, y_tile_limit) As String * z_stack_limit ' for a maximum of z_stack_limit items per tile
  85.     Dim xPos As Integer
  86.     Dim yPos As Integer
  87.     Dim zPos As Integer
  88.     Dim in$
  89.  
  90.     Objects(10, 10) = Chr$(1) ' Item 1 is on the bottom of the stack
  91.     Objects(10, 10) = Objects(10, 10) + Chr$(23) ' Item 23 is on top of it
  92.     Objects(10, 10) = Objects(10, 10) + Chr$(107) ' Item 107 is above that one
  93.     Objects(10, 10) = Objects(10, 10) + Chr$(114) ' And item 114 is rendered last and is the top of the stack
  94.     ' And the next 6 characters are all blank
  95.  
  96.     ' Putting them into your tile would be that simple, and getting the info back out would just be:
  97.  
  98.     For xPos = 0 To 2 ' x_tile_limit
  99.         For yPos = 0 To 2 ' y_tile_limit
  100.             Print "(" + cstr$(xPos) + "," + cstr$(yPos) + ")"
  101.             For zPos = 1 To z_stack_limit
  102.                 If Asc(Objects(xPos, yPos), zPos) <> 0 Then
  103.                     ' Do whatever you do with that object
  104.                     Print "    " + cstr$(zPos) + " = " + Chr$(34) + Mid$(Objects(xPos, yPos), zPos, 1) + Chr$(34)
  105.                 End If
  106.             Next zPos
  107.         Next yPos
  108.     Next xPos
  109.     'NEXT z, y, x
  110.  
  111.     Input "Press <ENTER> to continue"; in$
  112.  
  113. End Sub ' ArrayOfStacks1
  114.  
  115. ' /////////////////////////////////////////////////////////////////////////////
  116. ' Re: good way to implement an array of stacks/linked lists (for 2D tile-based game)?
  117. ' https://www.qb64.org/forum/index.php?topic=4008.0
  118.  
  119. ' Petr
  120. ' « Reply #3 on: Today at 03:34:47 PM »
  121.  
  122. ' Easiest it is using TYPE. Just create usertype field structure. Something as this:
  123.  
  124. Sub ArrayOfStacks2
  125.     ReDim UserF(100, 100) As Array2D
  126.     Dim xPos As Integer
  127.     Dim yPos As Integer
  128.     Dim in$
  129.  
  130.     For xPos = 1 To 100
  131.         For yPos = 1 To 100
  132.             ' fill grid X and Y positions
  133.             UserF(xPos, yPos).X_POS = xPos
  134.             UserF(xPos, yPos).Y_POS = yPos
  135.  
  136.             UserF(xPos, yPos).Grid.texture = Rnd * 1000 ' _LOADIMAGE....
  137.             UserF(xPos, yPos).Grid.Dynamic_Value = Rnd * 75 ' _SNDOPEN.....
  138.         Next yPos
  139.     Next xPos
  140.  
  141.     For xPos = 1 To 5
  142.         For yPos = 1 To 5
  143.             Print "(" + cstr$(xPos) + "," + cstr$(yPos) + ")"
  144.             Print "    .X_POS=" + cstr$(UserF(xPos, yPos).X_POS)
  145.             Print "    .Y_POS=" + cstr$(UserF(xPos, yPos).X_POS)
  146.             Print "    .Grid.texture=" + cstr$(UserF(xPos, yPos).Grid.texture)
  147.             Print "    .Grid.Dynamic_Value=" + cstr$(UserF(xPos, yPos).Grid.Dynamic_Value)
  148.         Next yPos
  149.     Next xPos
  150.  
  151.     Input "Press <ENTER> to continue"; in$
  152. End Sub ' ArrayOfStacks2
  153.  
  154. ' /////////////////////////////////////////////////////////////////////////////
  155.  
  156. ' Simplest approach where each tile is limited to the size of the string,
  157. ' to get the number of objects on a tile, do
  158. '     iNumObjects = LEN(Objects(xPos, yPos)),
  159.  
  160. Sub ArrayOfStacks3
  161.     ReDim arrMap(10, 10) As String
  162.     Dim xPos As Integer
  163.     Dim yPos As Integer
  164.     Dim iCount As Integer
  165.     Dim iLoop As Integer
  166.     Dim iChar As Integer
  167.     Dim sNext As String
  168.     Dim sLine As String
  169.     Dim sRemoved As String
  170.     Dim in$
  171.  
  172.     ' POPULATE MAP WITH A RANDOM # OF OBJECTS ON EACH TILE
  173.     ' USING "PUSH" ROUTINE
  174.     For xPos = 1 To 10
  175.         For yPos = 1 To 10
  176.             iCount = RandomNumber%(1, 20)
  177.             If iCount < 6 Then
  178.                 For iLoop = 1 To iCount
  179.                     iChar = RandomNumber%(65, 90)
  180.                     DoPush arrMap(), xPos, yPos, Chr$(iChar)
  181.                 Next iLoop
  182.             Else
  183.                 arrMap(xPos, yPos) = ""
  184.             End If
  185.         Next yPos
  186.     Next xPos
  187.  
  188.     ' PRINT THE INITIAL MAP
  189.     Print "CONTENTS BEFORE REMOVE:"
  190.     sLine = ""
  191.     For yPos = 1 To 10
  192.         For xPos = 1 To 10
  193.             ' WRAP OUTPUT TO 80 COLUMNS
  194.             ' MAX SIZE EACH ENTRY = 16: (10,10)="ABCDE"
  195.             '                           1234567890123456
  196.             If Len(sLine) > 64 Then
  197.                 Print sLine
  198.                 sLine = ""
  199.             End If
  200.             If Len(arrMap(xPos, yPos)) > 0 Then
  201.                 sLine = sLine + "(" + cstr$(xPos) + "," + cstr$(yPos) + ")=" + Chr$(34) + arrMap(xPos, yPos) + Chr$(34) + " "
  202.             End If
  203.         Next xPos
  204.     Next yPos
  205.     Print sLine
  206.     Input "Press <ENTER> to continue"; in$
  207.  
  208.     ' REMOVE ONE OBJECT OFF THE TOP OF EACH TILE
  209.     ' USING "POP" FUNCTION
  210.     Print
  211.     Print "REMOVED ONE FROM THE TOP OF EACH:"
  212.     sRemoved = ""
  213.     sLine = ""
  214.     For yPos = 1 To 10
  215.         For xPos = 1 To 10
  216.             If Len(sLine) > 64 Then
  217.                 Print sLine
  218.                 sLine = ""
  219.             End If
  220.             If Len(arrMap(xPos, yPos)) > 0 Then
  221.                 sNext = DoPop$(arrMap(), xPos, yPos)
  222.                 sRemoved = sRemoved + sNext
  223.                 sLine = sLine + "(" + cstr$(xPos) + "," + cstr$(yPos) + ")=" + Chr$(34) + arrMap(xPos, yPos) + Chr$(34) + " "
  224.             End If
  225.         Next xPos
  226.     Next yPos
  227.     Print sLine
  228.  
  229.     Print
  230.     Print "ALL REMOVED:"
  231.     sLine = ""
  232.     For iLoop = 1 To Len(sRemoved)
  233.         If Len(sLine) > 64 Then
  234.             Print sLine
  235.             sLine = ""
  236.         End If
  237.         sLine = sLine + IIFSTR$(iLoop > 1, ", ", "") + Mid$(sRemoved, iLoop, 1)
  238.     Next iLoop
  239.     Print sLine
  240.     Input "Press <ENTER> to continue"; in$
  241.  
  242. End Sub ' ArrayOfStacks3
  243.  
  244. ' /////////////////////////////////////////////////////////////////////////////
  245.  
  246. Sub DoPush (arrTiles() As String, xPos As Integer, yPos As Integer, sObject As String)
  247.     arrTiles(xPos, yPos) = arrTiles(xPos, yPos) + sObject
  248. End Sub ' DoPush
  249.  
  250. ' /////////////////////////////////////////////////////////////////////////////
  251.  
  252. Function DoPop$ (arrTiles() As String, xPos As Integer, yPos As Integer)
  253.     Dim iLen As Integer
  254.     iLen = Len(arrTiles(xPos, yPos))
  255.     If iLen > 0 Then
  256.         If iLen = 1 Then
  257.             DoPop$ = arrTiles(xPos, yPos)
  258.             arrTiles(xPos, yPos) = ""
  259.         Else
  260.             DoPop$ = Right$(arrTiles(xPos, yPos), 1)
  261.             arrTiles(xPos, yPos) = Left$(arrTiles(xPos, yPos), Len(arrTiles(xPos, yPos)) - 1)
  262.         End If
  263.     Else
  264.         DoPop$ = ""
  265.     End If
  266. End Function ' DoPop$
  267.  
  268. ' /////////////////////////////////////////////////////////////////////////////
  269.  
  270. Sub DumpAsciiValues
  271.     Dim iLoop As Integer
  272.     Dim sLine As String
  273.     Dim in$
  274.  
  275.     sLine = ""
  276.     For iLoop = 32 To 127
  277.         If Len(sLine) > 72 Then
  278.             Print sLine
  279.             sLine = ""
  280.         End If
  281.         sLine = sLine + cstr$(iLoop) + "=" + Chr$(34) + Chr$(iLoop) + Chr$(34) + IIFSTR$(iLoop < 127, ",", "")
  282.     Next iLoop
  283.     Print sLine
  284.  
  285.     Input "Press <ENTER> to continue"; in$
  286. End Sub ' DumpAsciiValues
  287.  
  288. ' /////////////////////////////////////////////////////////////////////////////
  289. ' Convert a value to string and trim it (because normal Str$ adds spaces)
  290.  
  291. Function cstr$ (myValue)
  292.     'cstr$ = LTRIM$(RTRIM$(STR$(myValue)))
  293.     cstr$ = _Trim$(Str$(myValue))
  294. End Function ' cstr$
  295.  
  296. ' /////////////////////////////////////////////////////////////////////////////
  297.  
  298. Function IIF (Condition, IfTrue, IfFalse)
  299.     If Condition Then IIF = IfTrue Else IIF = IfFalse
  300.  
  301. ' /////////////////////////////////////////////////////////////////////////////
  302.  
  303. Function IIFSTR$ (Condition, IfTrue$, IfFalse$)
  304.     If Condition Then IIFSTR$ = IfTrue$ Else IIFSTR$ = IfFalse$
  305.  
  306. ' /////////////////////////////////////////////////////////////////////////////
  307. ' Generate random value between Min and Max.
  308. Function RandomNumber% (Min%, Max%)
  309.     Dim NumSpread%
  310.  
  311.     ' SET RANDOM SEED
  312.     'Randomize ' Initialize random-number generator.
  313.  
  314.     ' GET RANDOM # Min%-Max%
  315.     'RandomNumber = Int((Max * Rnd) + Min) ' generate number
  316.  
  317.     NumSpread% = (Max% - Min%) + 1
  318.  
  319.     RandomNumber% = Int(Rnd * NumSpread%) + Min% ' GET RANDOM # BETWEEN Max% AND Min%
  320.  
  321. End Function ' RandomNumber%
  322.  

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
If memory is a concern, then STRING isn’t the way to go.  Every string variable in QB64 has quite a bit of overhead associated with it.  (A pointer to the data, a variable to hold length of data, maybe some other stuff which I don’t remember.)  If my memory isn’t failing (and it usually is), every STRING has an internal 32-byte data type that it maps to (qbs type).

So DIM x(1 TO 10) uses 320 bytes memory, on top of whatever data it holds.

Usually this isn’t a big deal, but a 2d array of some size could have a decent amount of overhead associated with it.

DIM tile (x, y, z) AS _BYTE could conceivably use less memory than DIM tile(x, y) AS STRING just from the amount of memory required to track all those pointers and lengths.

Edit -- 20 bytes, I think, if my math is correct:

    struct qbs{
        uint8 *chr;//a 32 bit pointer to the string's data
        int32 len;//must be signed for comparisons against signed int32s
        uint8 in_cmem;//set to 1 if in the conventional memory DBLOCK
        uint16 *cmem_descriptor;
        uint16 cmem_descriptor_offset;
        uint32 listi;//the index in the list of strings that references it
        uint8 tmp;//set to 1 if the string can be deleted immediately after being processed
        uint32 tmplisti;//the index in the list of strings that references it
        uint8 fixed;//fixed length string
        uint8 readonly;//set to 1 if string is read only
        qbs_field *field;
    };
« Last Edit: June 28, 2021, 05:03:18 pm by SMcNeill »
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline johannhowitzer

  • Forum Regular
  • Posts: 118
    • View Profile
I would simply store a general stack of objects, and in that stack, store their x and y coordinates.  This imposes no limit on each individual tile, only on the whole map, and unless you have a huge limit for the whole map, saves a lot of data space.

Each object can then carry its own attributes, or simply be an index referring to an array containing all the various objects and their preset attributes.

EDIT: Ok, now that I'm home from work, I can expand on this a little.  You can have something like the following:

Code: QB64: [Select]
  1. type entity_spec_structure
  2.    ' stuff about entities in here
  3. dim shared entity_spec(100) as entity_spec_structure ' preset data for entities that can be on tiles in your map
  4.  
  5. type entity_structure
  6.    i as integer ' index from entity_spec() represented by this entity
  7.    x as integer ' position on the map
  8.    y as integer
  9. dim shared entity(100) as entity_structure
  10. dim shared entity_count as integer

Now you can add things to this stack, and then use the coordinates to draw the appropriate sprites, look through for a matching position if the player's trying to pick up an item from the tile they're standing on, etc.  Doing collision becomes O(n^2) time complexity if you're doing pairwise comparisons, but you can add a pre-processing array back in if you want, it will again limit how many things can be on a given tile, but removes the need for pairwise:

Code: QB64: [Select]
  1. dim shared map_objects([x], [y], [n]) as integer ' First two dimensions are coordinates, last carries the stack, value is the entity index
  2.  
  3. ' Wipe the array first
  4. for x = 1 to [map width]
  5.    for y = 1 to [map height]
  6.       for n = 1 to [tile object limit]
  7.          map_objects(x, y, n) = false
  8.       next n
  9.    next y
  10.  
  11. ' Put each entity on its tile
  12. for e = 1 to entity_count
  13.    n = 1
  14.    do until map_objects(entity(e).x, entity(e).y, n) = false
  15.       if n > [tile object limit] then _continue
  16.       n = n + 1
  17.    loop
  18.    map_objects(entity(n).x, entity(n).y, n) = e

Now, when you go to do collision, you can run through the entities in index order, and only check the above pre-processed array at the relevant coordinates, until you run into one that is false.  You're not storing the whole specifications of every object on the grid, just indices referring to them.  This puts a pretty low upper bound on how much needs to be done every time collision is checked.  Hope that makes sense.
« Last Edit: June 29, 2021, 05:48:45 am by johannhowitzer »

Offline madscijr

  • Seasoned Forum Regular
  • Posts: 295
    • View Profile
If memory is a concern, then STRING isn’t the way to go.
...
DIM tile (x, y, z) AS _BYTE could conceivably use less memory

Memory isn't so much a concern as speed.
However a stack of _BYTE would be sufficient and preferable to a stack of string characters,
which I would want to convert to a numeric value anyway (an index against some array of objects in the game).

The most important thing is being able to qucikly index the stack of objects at a given x,y coordinate, and that the stack maintains its order.
I'm going to try implementing a simple stack of integers using an array and see how that looks.

Offline madscijr

  • Seasoned Forum Regular
  • Posts: 295
    • View Profile
I would simply store a general stack of objects, and in that stack, store their x and y coordinates.

Thanks for your reply and taking the time to share that example, I'll give it a look.
The concern I have with this method, is that you have to loop through the whole stack testing the x and y for each item, to find all items for a given (x,y).
You talk about a pre-processed array, is that to speed up that process? I didn't quite follow it in your explanation, but I will understand better once I look at your code in the QB64 editor and can run it & play with it.
Thanks again!

Offline SMcNeill

  • QB64 Developer
  • Forum Resident
  • Posts: 3972
    • View Profile
    • Steve’s QB64 Archive Forum
Another way to think about this, is conceivably from the items perspective.

Let's say I have a map of 100x100 tiles.  The vast majority of those are going to simply be "base tiles"; the floor, wall, grass, water, ect...

Now, above those tiles, I'm going to have the layers of objects which you're talking about -- table graphic, chair, creature, ect...

The 100 x 100 grid gives us 10,000 points of data, even if we track a blank number of objects imposed upon it, such as with DIM TileObject(100, 100) AS STRING, as you mentioned above.

For speed concerns, might it not be better to instead simply track the objects themselves upon the map?

TYPE ObjectType
    item AS _BYTE (item id)
    x AS _BYTE
    y AS _BYTE
END TYPE

REDIM Objects(0) AS ObjectType

Then, if I place 100 items on the map (ignoring the vast majority of walls, water, and ceiling tiles), I'd only need to cycle through that list of 100 items from top to bottom, to place my objects onto the map.  Object(1) might be a table.  Object(2) might be a book at the same X/Y coordinates, so it renders on top of the table.  Object(3) might be a coin sitting on top of the book...

I suppose the thing to be thinking about is if there's going to be less tiles to process for objects, or less objects to process than tiles.  The answer to that question might affect the efficiency of your data structure significantly.
https://github.com/SteveMcNeill/Steve64 — A github collection of all things Steve!

Offline Cobalt

  • QB64 Developer
  • Forum Resident
  • Posts: 878
  • At 60 I become highly radioactive!
    • View Profile
knowing nothing about just what kind of game your working on all I could do is give you an example from my current project Phantasy Star.

Code: QB64: [Select]
  1. TYPE TownTiles
  2.  Is_Walkable AS _BYTE
  3.  Is_Animated AS _BYTE
  4.  Is_NPC_Location AS _BYTE 'where the NPCs stand this links to the NPC array
  5.  Is_Building AS _UNSIGNED _BYTE 'covers shops and houses and caves(in towns) and churches
  6.  Is_Exit AS _BYTE
  7.  Search_Item AS _BYTE 'is there something to find? Most of these require a Flag set too
  8.  Dialogs AS INTEGER 'used with NPCs in Building for conversations
  9.  Setting AS _BYTE 'building inside and npc image to use
  10.  
  11. TYPE NPC_Dialog
  12.  Map AS _BYTE
  13.  X AS _BYTE
  14.  Y AS _BYTE
  15.  Kind AS _BYTE 'standard dialog or special? Special requires item or flag
  16.  Map_Sprite AS _BYTE 'map display sprite
  17.  Scene_Setup AS _UNSIGNED _BYTE 'scene background and NPC sprite when entering dialog
  18.  Dialog AS _BYTE 'link to array of dialog spoken by NPCs
  19.  
  20. DIM SHARED TownMap(9, 54, 39) AS TownTiles
  21. DIM SHARED NPC(127) as NPC_Dialog
  22.  

So here you have the Town Array and Type, as well as the NPC Array and Type, 2 items needed for the map but kept separate for size and access ease(if only we had Arrays in UDTs :( ). so if you look at the TownTile Type you will see Is_NPC_Location, so while the townmap array knows where the npcs are it doesn't hold their actual information, just a value that points to the NPC array.
Now this approach works cause the NPCs in this game do not move. In Dragon Warrior the NPCs were mobile so the townmap arrays there had no information on them at all, it was all handled in the NPC data with extra collision functions to handle their locations.

So depending on what you need to store, static items or moving creatures, will determine how you want to implement things. If its just like items that are pre-generate when the program starts or placed down by the player during game play then a TYPE system that links to an array would probably be a good idea, along with a limit on the max number of items any tile can hold at any one time(say 127 or 255 to use _BYTE or _UNSIGNED _BYTE) However,
If its something that can change location like an NPC or Creature then you would be better off not storing that information with each tile but in a separate array for those things. And processing them separately with their own code.

and to help keep speed in check only process what can be seen, as with this example:
Code: QB64: [Select]
  1. SUB Display_NPC (Ax%%, AY%%, Sx%, Sy%)
  2.  'sx and sy are used when player moves to Scroll the screen
  3.  'ax adn ay players current map x and y to shift npc locations
  4.  Mx%% = Ax%% - 9 '-9 to center on player character
  5.  My%% = AY%% - 7 '-7 to center(ish) on player character
  6.  FOR i%% = 1 TO 10
  7.   IF NPC(i%%).Map = P(0).Is_Intown THEN
  8.    IF NPC(i%%).Map_Sprite = 6 THEN
  9.     _PUTIMAGE (Sx% + (NPC(i%%).X - Mx%%) * 32, Sy% + (NPC(i%%).Y - My%%) * 32 - 30)-STEP(45, 45), Layer(7), Layer(1), (0 + 17 * NPC(i%%).Map_Sprite, 180)-STEP(23, 23)
  10.    ELSE
  11.     _PUTIMAGE (Sx% + (NPC(i%%).X - Mx%%) * 32, Sy% + (NPC(i%%).Y - My%%) * 32 - 30)-STEP(31, 45), Layer(7), Layer(1), (0 + 17 * NPC(i%%).Map_Sprite, 180)-STEP(15, 23)
  12.    END IF
  13.   END IF
  14.  NEXT i%%
  15.  

now at the moment I only have 10 NPCs done, so I don't have any speed worries. You can see though that I have already implemented a control:
IF NPC(i%%).Map = P(0).Is_Intown THEN
So I am only processing things that are in the same town map as the player. I could take this even farther by only looking for things that are in the players view, as you can see the map is centered around the player. So if this was a huge map of 10s of thousands tiles with thousands of Objects to deal with you would certainly want to limit how many the program deals with at any one time. if your only going to have a few hundred objects globally then dont worry about it. In Dragon Warrior I only had, i think, 117 NPCs so I processed their movement and collision all the time when the player was in towns, while only displaying the ones on the current map, with out any speed issues and probably could have handled 3 or 4 times that number easily.

I know this isn't technically Bill's "Linked Lists" thing  but its my view as where you use a value in one array to look at another (to look at another and so on if needed) where what is in that second array can change without affecting the first.
Granted after becoming radioactive I only have a half-life!

Offline madscijr

  • Seasoned Forum Regular
  • Posts: 295
    • View Profile
A few of you had talked about storing a pointer in the map array to a second array,
so buidling on that and the string-based stack (method #3),
I came up with a working stack of UDTs - code below, see method #5!

The POP uses a soft delete, and PUSH recycles any previously deleted items hanging around,
which I think helps with performance (I foresee there being roughly an equal # of inserts/deletes in my game).

Thanks for your suggestions - any comments welcome.
Code: QB64: [Select]
  1. ' OPTIONS
  2.  
  3. ' GLOBAL DECLARATIONS a$=string, i%=integer, L&=long, s!=single, d#=double
  4. Const FALSE = 0
  5. Const TRUE = Not FALSE
  6.  
  7. ' FOR Petr's suggestion #2:
  8. Type UserField
  9.     texture As Long
  10.     Dynamic_Value As Long
  11. Type Array2D
  12.     Grid As UserField
  13.     X_POS As Long
  14.     Y_POS As Long
  15.  
  16. ' FOR ArrayOfStacks4
  17. Type entity_spec_structure
  18.     name As String
  19.     value As Integer
  20. Type entity_structure
  21.     i As Integer ' index from entity_spec() represented by this entity
  22.     x As Integer ' position on the map
  23.     y As Integer
  24.  
  25. ' FOR ArrayOfStacks5
  26. Type StackNodeType
  27.     name As String
  28.     value As Integer
  29.     NextIndex As Integer
  30.     'IsDeleted AS INTEGER <- not needed but you can add this if you want a way to quickly test if a node is deleted
  31.     'xPos AS INTEGER <- not needed but you can add this if you want a way to quickly get a node's x position on the map
  32.     'yPos AS INTEGER <- not needed but you can add this if you want a way to quickly get a node's y position on the map
  33.  
  34.  
  35. ' INITIALIZE
  36. Dim Shared ProgramPath$: ProgramPath$ = Left$(Command$(0), _InStrRev(Command$(0), "\"))
  37. Dim Shared ProgramName$: ProgramName$ = Mid$(Command$(0), _InStrRev(Command$(0), "\") + 1)
  38.  
  39. ' RUN THE MAIN PROGRAM
  40. main
  41.  
  42. ' FINISH
  43. System ' return control to the operating system
  44. Print ProgramName$ + " finished."
  45.  
  46. ' /////////////////////////////////////////////////////////////////////////////
  47.  
  48. Sub main
  49.     Dim in$: in$ = ""
  50.     Do
  51.         Cls
  52.         Print ProgramName$
  53.         Print
  54.         Print "Test different ways to implement an array of stacks"
  55.         Print
  56.         Print "1. Method #1 = fixed length string"
  57.         Print
  58.         Print "2. Method #2 = user defined type"
  59.         Print
  60.         Print "3. Method #3 = simple string-based stack"
  61.         Print
  62.         Print "4. Method #4 = entity arrays(?) = not working"
  63.         Print
  64.         Print "5. Method #5 = array-based stack of objects (UDTs)"
  65.         Print
  66.         Print "What to do ('q' to exit)"
  67.  
  68.         Input in$: in$ = LCase$(Left$(in$, 1))
  69.  
  70.         If in$ = "1" Then
  71.             ArrayOfStacks1
  72.             _KeyClear: '_DELAY 1
  73.  
  74.         ElseIf in$ = "2" Then
  75.             ArrayOfStacks2
  76.             _KeyClear: '_DELAY 1
  77.  
  78.         ElseIf in$ = "3" Then
  79.             ArrayOfStacks3
  80.             _KeyClear: '_DELAY 1
  81.  
  82.         ElseIf in$ = "4" Then
  83.             _KeyClear: '_DELAY 1
  84.             Print "Under construction"
  85.  
  86.         ElseIf in$ = "5" Then
  87.             ArrayOfStacks5
  88.             _KeyClear: '_DELAY 1
  89.  
  90.         ElseIf in$ = "6" Then
  91.             _KeyClear: '_DELAY 1
  92.             Print "Under construction"
  93.  
  94.         End If
  95.     Loop Until in$ = "q"
  96. End Sub ' main
  97.  
  98. ' /////////////////////////////////////////////////////////////////////////////
  99. ' Re: good way to implement an array of stacks/linked lists (for 2D tile-based game)?
  100. ' https://www.qb64.org/forum/index.php?topic=4008.0
  101.  
  102. ' SMcNeill
  103. ' « Reply #2 on: Today at 03:28:41 PM »
  104.  
  105. ' Wouldn't the easiest way to do something like this be just:
  106.  
  107. Sub ArrayOfStacks1
  108.     Const x_tile_limit = 20
  109.     Const y_tile_limit = 20
  110.     Const z_stack_limit = 5
  111.  
  112.     'REDIM Objects(x_tile_limit, y_tile_limit) AS STRING * 10 'for a maximum of 10 items per tile
  113.     ReDim Objects(x_tile_limit, y_tile_limit) As String * z_stack_limit ' for a maximum of z_stack_limit items per tile
  114.     Dim xPos As Integer
  115.     Dim yPos As Integer
  116.     Dim zPos As Integer
  117.     Dim in$
  118.  
  119.     Objects(10, 10) = Chr$(1) ' Item 1 is on the bottom of the stack
  120.     Objects(10, 10) = Objects(10, 10) + Chr$(23) ' Item 23 is on top of it
  121.     Objects(10, 10) = Objects(10, 10) + Chr$(107) ' Item 107 is above that one
  122.     Objects(10, 10) = Objects(10, 10) + Chr$(114) ' And item 114 is rendered last and is the top of the stack
  123.     ' And the next 6 characters are all blank
  124.  
  125.     ' Putting them into your tile would be that simple, and getting the info back out would just be:
  126.  
  127.     For xPos = 0 To 2 ' x_tile_limit
  128.         For yPos = 0 To 2 ' y_tile_limit
  129.             Print "(" + cstr$(xPos) + "," + cstr$(yPos) + ")"
  130.             For zPos = 1 To z_stack_limit
  131.                 If Asc(Objects(xPos, yPos), zPos) <> 0 Then
  132.                     ' Do whatever you do with that object
  133.                     Print "    " + cstr$(zPos) + " = " + Chr$(34) + Mid$(Objects(xPos, yPos), zPos, 1) + Chr$(34)
  134.                 End If
  135.             Next zPos
  136.         Next yPos
  137.     Next xPos
  138.     'NEXT z, y, x
  139.  
  140.     Input "Press <ENTER> to continue"; in$
  141.  
  142. End Sub ' ArrayOfStacks1
  143.  
  144. ' /////////////////////////////////////////////////////////////////////////////
  145. ' Re: good way to implement an array of stacks/linked lists (for 2D tile-based game)?
  146. ' https://www.qb64.org/forum/index.php?topic=4008.0
  147.  
  148. ' Petr
  149. ' « Reply #3 on: Today at 03:34:47 PM »
  150.  
  151. ' Easiest it is using TYPE. Just create usertype field structure. Something as this:
  152.  
  153. Sub ArrayOfStacks2
  154.     ReDim UserF(100, 100) As Array2D
  155.     Dim xPos As Integer
  156.     Dim yPos As Integer
  157.     Dim in$
  158.  
  159.     For xPos = 1 To 100
  160.         For yPos = 1 To 100
  161.             ' fill grid X and Y positions
  162.             UserF(xPos, yPos).X_POS = xPos
  163.             UserF(xPos, yPos).Y_POS = yPos
  164.  
  165.             UserF(xPos, yPos).Grid.texture = Rnd * 1000 ' _LOADIMAGE....
  166.             UserF(xPos, yPos).Grid.Dynamic_Value = Rnd * 75 ' _SNDOPEN.....
  167.         Next yPos
  168.     Next xPos
  169.  
  170.     For xPos = 1 To 5
  171.         For yPos = 1 To 5
  172.             Print "(" + cstr$(xPos) + "," + cstr$(yPos) + ")"
  173.             Print "    .X_POS=" + cstr$(UserF(xPos, yPos).X_POS)
  174.             Print "    .Y_POS=" + cstr$(UserF(xPos, yPos).X_POS)
  175.             Print "    .Grid.texture=" + cstr$(UserF(xPos, yPos).Grid.texture)
  176.             Print "    .Grid.Dynamic_Value=" + cstr$(UserF(xPos, yPos).Grid.Dynamic_Value)
  177.         Next yPos
  178.     Next xPos
  179.  
  180.     Input "Press <ENTER> to continue"; in$
  181. End Sub ' ArrayOfStacks2
  182.  
  183. ' /////////////////////////////////////////////////////////////////////////////
  184.  
  185. ' Simplest approach where each tile is limited to the size of the string,
  186. ' to get the number of objects on a tile, do
  187. '     iNumObjects = LEN(Objects(xPos, yPos)),
  188.  
  189. Sub ArrayOfStacks3
  190.     ReDim arrMap(10, 10) As String
  191.     Dim xPos As Integer
  192.     Dim yPos As Integer
  193.     Dim iCount As Integer
  194.     Dim iLoop As Integer
  195.     Dim iChar As Integer
  196.     Dim sNext As String
  197.     Dim sLine As String
  198.     Dim sRemoved As String
  199.     Dim in$
  200.  
  201.     ' POPULATE MAP WITH A RANDOM # OF OBJECTS ON EACH TILE
  202.     ' USING "PUSH" ROUTINE
  203.     For xPos = 1 To 10
  204.         For yPos = 1 To 10
  205.             iCount = RandomNumber%(1, 20)
  206.             If iCount < 6 Then
  207.                 For iLoop = 1 To iCount
  208.                     iChar = RandomNumber%(65, 90)
  209.                     DoPush3 arrMap(), xPos, yPos, Chr$(iChar)
  210.                 Next iLoop
  211.             Else
  212.                 arrMap(xPos, yPos) = ""
  213.             End If
  214.         Next yPos
  215.     Next xPos
  216.  
  217.     ' PRINT THE INITIAL MAP
  218.     Print "CONTENTS BEFORE REMOVE:"
  219.     sLine = ""
  220.     For yPos = 1 To 10
  221.         For xPos = 1 To 10
  222.             ' WRAP OUTPUT TO 80 COLUMNS
  223.             ' MAX SIZE EACH ENTRY = 16: (10,10)="ABCDE"
  224.             '                           1234567890123456
  225.             If Len(sLine) > 64 Then
  226.                 Print sLine
  227.                 sLine = ""
  228.             End If
  229.             If Len(arrMap(xPos, yPos)) > 0 Then
  230.                 sLine = sLine + "(" + cstr$(xPos) + "," + cstr$(yPos) + ")=" + Chr$(34) + arrMap(xPos, yPos) + Chr$(34) + " "
  231.             End If
  232.         Next xPos
  233.     Next yPos
  234.     Print sLine
  235.     Input "Press <ENTER> to continue"; in$
  236.  
  237.     ' REMOVE ONE OBJECT OFF THE TOP OF EACH TILE
  238.     ' USING "POP" FUNCTION
  239.     Print
  240.     Print "REMOVED ONE FROM THE TOP OF EACH:"
  241.     sRemoved = ""
  242.     sLine = ""
  243.     For yPos = 1 To 10
  244.         For xPos = 1 To 10
  245.             If Len(sLine) > 64 Then
  246.                 Print sLine
  247.                 sLine = ""
  248.             End If
  249.             If Len(arrMap(xPos, yPos)) > 0 Then
  250.                 sNext = DoPop3$(arrMap(), xPos, yPos)
  251.                 sRemoved = sRemoved + sNext
  252.                 sLine = sLine + "(" + cstr$(xPos) + "," + cstr$(yPos) + ")=" + Chr$(34) + arrMap(xPos, yPos) + Chr$(34) + " "
  253.             End If
  254.         Next xPos
  255.     Next yPos
  256.     Print sLine
  257.  
  258.     Print
  259.     Print "ALL REMOVED:"
  260.     sLine = ""
  261.     For iLoop = 1 To Len(sRemoved)
  262.         If Len(sLine) > 64 Then
  263.             Print sLine
  264.             sLine = ""
  265.         End If
  266.         sLine = sLine + IIFSTR$(iLoop > 1, ", ", "") + Mid$(sRemoved, iLoop, 1)
  267.     Next iLoop
  268.     Print sLine
  269.     Input "Press <ENTER> to continue"; in$
  270.  
  271. End Sub ' ArrayOfStacks3
  272.  
  273. ' /////////////////////////////////////////////////////////////////////////////
  274.  
  275. Sub DoPush3 (arrTiles() As String, xPos As Integer, yPos As Integer, sObject As String)
  276.     arrTiles(xPos, yPos) = arrTiles(xPos, yPos) + sObject
  277. End Sub ' DoPush3
  278.  
  279. ' /////////////////////////////////////////////////////////////////////////////
  280.  
  281. Function DoPop3$ (arrTiles() As String, xPos As Integer, yPos As Integer)
  282.     Dim iLen As Integer
  283.     iLen = Len(arrTiles(xPos, yPos))
  284.     If iLen > 0 Then
  285.         If iLen = 1 Then
  286.             DoPop3$ = arrTiles(xPos, yPos)
  287.             arrTiles(xPos, yPos) = ""
  288.         Else
  289.             DoPop3$ = Right$(arrTiles(xPos, yPos), 1)
  290.             arrTiles(xPos, yPos) = Left$(arrTiles(xPos, yPos), Len(arrTiles(xPos, yPos)) - 1)
  291.         End If
  292.     Else
  293.         DoPop3$ = ""
  294.     End If
  295. End Function ' DoPop3$
  296.  
  297. ' /////////////////////////////////////////////////////////////////////////////
  298.  
  299. Sub DumpAsciiValues
  300.     Dim iLoop As Integer
  301.     Dim sLine As String
  302.     Dim in$
  303.  
  304.     sLine = ""
  305.     For iLoop = 32 To 127
  306.         If Len(sLine) > 72 Then
  307.             Print sLine
  308.             sLine = ""
  309.         End If
  310.         sLine = sLine + cstr$(iLoop) + "=" + Chr$(34) + Chr$(iLoop) + Chr$(34) + IIFSTR$(iLoop < 127, ",", "")
  311.     Next iLoop
  312.     Print sLine
  313.  
  314.     Input "Press <ENTER> to continue"; in$
  315. End Sub ' DumpAsciiValues
  316.  
  317. ' /////////////////////////////////////////////////////////////////////////////
  318. ' Re: good way to implement an array of stacks/linked lists (for 2D tile-based game)?
  319. ' https://www.qb64.org/forum/index.php?topic=4008.0
  320.  
  321. ' johannhowitzer
  322. ' « Reply #9 on: Yesterday at 10:46:48 PM »
  323.  
  324. ' I would simply store a general stack of objects, and in that stack,
  325. ' store their x and y coordinates.
  326. ' This imposes no limit on each individual tile, only on the whole map,
  327. ' and unless you have a huge limit for the whole map, saves a lot of data
  328. ' space.
  329. '
  330. ' Each object can then carry its own attributes, or simply be an index
  331. ' referring to an array containing all the various objects and their
  332. ' preset attributes.
  333. '
  334. ' EDIT: Ok, now that I'm home from work, I can expand on this a little.
  335. ' You can have something like the following:
  336. '
  337. ' Now you can add things to this stack, and then use the coordinates to draw
  338. ' the appropriate sprites, look through for a matching position if the
  339. ' player's trying to pick up an item from the tile they're standing on, etc.
  340. ' Doing collision becomes O(n^2) time complexity if you're doing pairwise
  341. ' comparisons, but you can add a pre-processing array back in if you want,
  342. ' it will again limit how many things can be on a given tile, but removes
  343. ' the need for pairwise:
  344. '
  345. ' Now, when you go to do collision, you can run through the entities in
  346. ' index order, and only check the above pre-processed array at the relevant
  347. ' coordinates, until you run into one that is false.  You're not storing the
  348. ' whole specifications of every object on the grid, just indices referring to
  349. ' them. This puts a pretty low upper bound on how much needs to be done every
  350. ' time collision is checked.  Hope that makes sense.
  351.  
  352. ' COMMENTS:
  353. ' This seems overly complicated. Do we really need entity(100)?
  354. Sub ArrayOfStacks4
  355.     Dim map_objects(10, 10, 10) As Integer ' First two dimensions are coordinates, last carries the stack, value is the entity index
  356.     Dim entity_spec(100) As entity_spec_structure ' preset data for entities that can be on tiles in your map
  357.     Dim entity(100) As entity_structure
  358.     Dim entity_count As Integer
  359.     Dim x As Integer
  360.     Dim y As Integer
  361.     Dim n As Integer
  362.     Dim e As Integer
  363.     Dim in$
  364.  
  365.     ' Wipe the array first
  366.     For x = 1 To 10 ' [map width]
  367.         For y = 1 To 10 ' [map height]
  368.             For n = 1 To 10 ' [tile object limit]
  369.                 map_objects(x, y, n) = FALSE
  370.             Next n
  371.         Next y
  372.     Next x
  373.  
  374.     ' Put each entity on its tile
  375.     entity_count = 10 ' we will drop 10 of these
  376.     n = 0
  377.     For e = 1 To entity_count
  378.         ' entity_spec_structure values:
  379.         entity_spec(e).name = "entity #" + cstr$(e)
  380.         entity_spec(e).value = RandomNumber%(0, 100)
  381.  
  382.         ' entity_structure values:
  383.         n = n + 1
  384.         x = RandomNumber%(1, 10)
  385.         y = RandomNumber%(1, 10)
  386.         entity(n).i = e
  387.         entity(n).x = x
  388.         entity(n).y = y
  389.  
  390.         ' add to map:
  391.         map_objects(entity(n).x, entity(n).y, n) = e
  392.     Next e
  393.  
  394.     ' Show results
  395.     For y = 1 To 10 ' [map height]
  396.         For x = 1 To 10 ' [map width]
  397.             For n = 1 To 10 ' [tile object limit]
  398.                 If map_objects(x, y, n) <> FALSE Then
  399.                     e = map_objects(entity(n).x, entity(n).y, n)
  400.  
  401.                 End If
  402.             Next n
  403.         Next x
  404.     Next y
  405.  
  406.     Input "Press <ENTER> to continue"; in$
  407. End Sub ' ArrayOfStacks4
  408.  
  409. ' /////////////////////////////////////////////////////////////////////////////
  410.  
  411. Sub ArrayOfStacks5
  412.     Dim arrMap(5, 5) As Integer
  413.     ReDim arrStack(0) As StackNodeType ' index 0 holds next empty/deleted node
  414.     Dim xPos As Integer
  415.     Dim yPos As Integer
  416.     Dim iCount As Integer
  417.     Dim iLoop As Integer
  418.     Dim sName As String
  419.     Dim iValue As Integer
  420.     Dim iTotal As Integer
  421.     Dim MyIndex As Integer
  422.     Dim NewTop As Integer
  423.     Dim sChanged As String
  424.     Dim sLine As String
  425.     Dim iPrintCount As Integer
  426.     Dim in$
  427.  
  428.     ' INITIALIZE STACK
  429.     arrStack(0).NextIndex = 0 ' values > -1 points to next available node
  430.     arrStack(0).name = "NextFree"
  431.  
  432.     ' POPULATE MAP WITH A RANDOM # OF OBJECTS ON EACH TILE
  433.     ' USING "PUSH" ROUTINE
  434.     iTotal = 0
  435.     For xPos = 1 To 5
  436.         For yPos = 1 To 5
  437.             iCount = RandomNumber%(1, 20)
  438.             If iCount < 4 Then
  439.                 MyIndex = 0
  440.                 For iLoop = 1 To iCount
  441.                     iTotal = iTotal + 1
  442.                     sName = "item" + cstr$(iTotal)
  443.                     iValue = RandomNumber%(0, 100)
  444.                     MyIndex = DoPush5%(arrStack(), MyIndex, sName, iValue)
  445.                     arrMap(xPos, yPos) = MyIndex
  446.                 Next iLoop
  447.             Else
  448.                 arrMap(xPos, yPos) = 0
  449.             End If
  450.         Next yPos
  451.     Next xPos
  452.  
  453.     ' PRINT THE INITIAL MAP
  454.     Print "CONTENTS BEFORE REMOVE:"
  455.     For yPos = 1 To 5
  456.         For xPos = 1 To 5
  457.             If arrMap(xPos, yPos) > 0 Then
  458.                 MyIndex = arrMap(xPos, yPos)
  459.                 Print DumpStack5$(arrStack(), MyIndex, "arrMap(" + cstr$(yPos) + "," + cstr$(xPos) + "): ")
  460.             End If
  461.         Next xPos
  462.     Next yPos
  463.     Print DumpStack5$(arrStack(), 0, "Next Free: ")
  464.     Print
  465.     Input "Press <ENTER> to continue"; in$
  466.     Print
  467.  
  468.     ' REMOVE ONE OBJECT OFF THE TOP OF EACH TILE
  469.     ' USING "POP" FUNCTION
  470.     sChanged = ""
  471.     sLine = ""
  472.     iPrintCount = 0
  473.     For yPos = 1 To 5
  474.         For xPos = 1 To 5
  475.             ' WRAP OUTPUT TO 80 COLUMNS
  476.             If Len(sLine) > 64 Then
  477.                 sChanged = sChanged + IIFSTR$(Len(sChanged) = 0, "", Chr$(10)) + "    " + sLine
  478.                 sLine = ""
  479.                 iPrintCount = 0
  480.             End If
  481.             If arrMap(xPos, yPos) > 0 Then
  482.                 MyIndex = arrMap(xPos, yPos)
  483.                 iPrintCount = iPrintCount + 1
  484.  
  485.                 sLine = sLine + IIFSTR$(iPrintCount > 1, ", ", "")
  486.                 sLine = sLine + cstr$(MyIndex) + "=" + arrStack(MyIndex).name + ">"
  487.                 sLine = sLine + cstr$(arrStack(MyIndex).NextIndex)
  488.  
  489.                 NewTop = DoPop5%(arrStack(), MyIndex)
  490.                 arrMap(xPos, yPos) = NewTop
  491.             End If
  492.         Next xPos
  493.     Next yPos
  494.     sChanged = sChanged + IIFSTR$(Len(sChanged) = 0, "", Chr$(10)) + "    " + sLine
  495.  
  496.     Print
  497.     Print "REMOVED ONE FROM THE TOP OF EACH:"
  498.     Print sChanged
  499.     Print
  500.     Input "Press <ENTER> to continue"; in$
  501.     Print
  502.  
  503.     ' PRINT MAP AFTER ITEMS REMOVED
  504.     Print "CONTENTS AFTER REMOVE:"
  505.     For yPos = 1 To 5
  506.         For xPos = 1 To 5
  507.             If arrMap(xPos, yPos) > 0 Then
  508.                 Print DumpStack5$(arrStack(), arrMap(xPos, yPos), "arrMap(" + cstr$(yPos) + "," + cstr$(xPos) + "): ")
  509.             End If
  510.         Next xPos
  511.     Next yPos
  512.     Print DumpStack5$(arrStack(), 0, "Next Free: ")
  513.     Print
  514.     Input "Press <ENTER> to continue"; in$
  515.     Print
  516.  
  517.     ' ADD ONE OBJECT TO THE TOP OF EACH TILE
  518.     ' USING "PUSH" FUNCTION
  519.     sChanged = ""
  520.     sLine = ""
  521.     For yPos = 1 To 5
  522.         For xPos = 1 To 5
  523.             ' WRAP OUTPUT TO 80 COLUMNS
  524.             If Len(sLine) > 64 Then
  525.                 sChanged = sChanged + IIFSTR$(Len(sChanged) = 0, "", Chr$(10)) + "    " + sLine
  526.                 sLine = ""
  527.             End If
  528.             If arrMap(xPos, yPos) > 0 Then
  529.                 MyIndex = arrMap(xPos, yPos)
  530.  
  531.                 iTotal = iTotal + 1
  532.                 sName = "item" + cstr$(iTotal)
  533.                 iValue = RandomNumber%(0, 100)
  534.                 MyIndex = DoPush5%(arrStack(), MyIndex, sName, iValue)
  535.                 arrMap(xPos, yPos) = MyIndex
  536.  
  537.                 sLine = sLine + cstr$(MyIndex) + "=" + arrStack(MyIndex).name + ">"
  538.                 sLine = sLine + cstr$(arrStack(MyIndex).NextIndex) + ", "
  539.             End If
  540.         Next xPos
  541.     Next yPos
  542.     sChanged = sChanged + IIFSTR$(Len(sChanged) = 0, "", Chr$(10)) + "    " + sLine
  543.  
  544.     Print
  545.     Print "ADDED ONE TO THE TOP OF EACH:"
  546.     Print sChanged
  547.     Print
  548.     Input "Press <ENTER> to continue"; in$
  549.     Print
  550.  
  551.     ' PRINT MAP AFTER ITEMS ADDED
  552.     Print "CONTENTS AFTER ADDING:"
  553.     For yPos = 1 To 5
  554.         For xPos = 1 To 5
  555.             If arrMap(xPos, yPos) > 0 Then
  556.                 Print DumpStack5$(arrStack(), arrMap(xPos, yPos), "arrMap(" + cstr$(yPos) + "," + cstr$(xPos) + "): ")
  557.             End If
  558.         Next xPos
  559.     Next yPos
  560.     Print DumpStack5$(arrStack(), 0, "Next Free: ")
  561.     Print
  562.     Input "Press <ENTER> to continue"; in$
  563.  
  564. End Sub ' ArrayOfStacks5
  565.  
  566. ' /////////////////////////////////////////////////////////////////////////////
  567. ' RECEIVES:
  568. ' arrStack = array of nodes representing multiple stacks
  569. ' MyIndex = node to drop new element on top of
  570. ' sName = string value for new node
  571. ' iValue = numeric value for new node
  572.  
  573. ' RETURNS:
  574. ' NextIndex = the new node's index in array arrStack
  575. ' (arrStack is updated by reference)
  576.  
  577. ' HOW THE STACK WORKS:
  578. '
  579. ' arrStack() holds multiple stacks, each "chain" has 1 or more node,
  580. ' where the top node's index is kept track of by whatever code is using this,
  581. ' for example a 2-D tilemap might holds the index of the top object at arrMap(x,y)
  582. '
  583. ' Index 0 is used to track deleted nodes,
  584. ' because we don't delete nodes from the arrStack, just reuse them.
  585. ' When a node is "deleted", we set arrStack(0) to the index of the deleted node.
  586. ' When we delete subsequent nodes, each one's .nextIndex points to the next available
  587. ' deleted node in a "chain". When we reuse the next given node,
  588. ' we repoint arrStack(0) to the next one, and so arrStack(0).nextIndex always points
  589. ' to the next available node to be reused.
  590. '
  591. ' If arrStack(0) = 0, there are no deleted nodes to be reused,
  592. ' then we append a new element to the array with REDIM _PRESERVE.
  593. '
  594. ' If MyIndex = 0 then a new chain is created,
  595. ' else the new value is added to the top of item MyIndex,
  596. ' and the new item's .nextIndex is set to point to MyIndex,
  597. ' so we have arrStack(n).nextIndex always points to the next item in that item's chain.
  598.  
  599. ' arrStack(0) is the head of the deleted/to be reused chain.
  600.  
  601. Function DoPush5% (arrStack() As StackNodeType, MyIndex As Integer, sName As String, iValue As Integer)
  602.     Dim NextIndex As Integer
  603.     Dim NextAfterIndex As Integer
  604.  
  605.     NextIndex = 0
  606.     If MyIndex > 0 Then
  607.         ' MAKE SURE WE HAVE THE ITEM MyIndex TO DROP ON TOP OF
  608.         If UBound(arrStack) >= MyIndex Then
  609.             ' DROP ON TOP OF ITEM MyIndex
  610.  
  611.             ' ADD NEW ITEM TO TOP OF CHAIN MyIndex
  612.             NextAfterIndex = MyIndex
  613.  
  614.             ' ANY EMPTY?
  615.             If arrStack(0).NextIndex > 0 Then
  616.                 ' reuse the empty one
  617.                 NextIndex = arrStack(0).NextIndex
  618.  
  619.                 ' move next empty to next available
  620.                 arrStack(0).NextIndex = arrStack(NextIndex).NextIndex
  621.             Else
  622.                 ' no empty, add a node
  623.                 ReDim _Preserve arrStack(UBound(arrStack) + 1) As StackNodeType
  624.                 NextIndex = UBound(arrStack)
  625.             End If
  626.  
  627.         Else
  628.             ' (ITEM MyIndex NOT FOUND, RETURN 0 INDICATING ERROR)
  629.         End If
  630.     Else
  631.         ' NOTHING TO DROP ON TOP OF, ADD TO A NEW CHAIN
  632.         NextAfterIndex = 0 ' this is the first one, no more
  633.  
  634.         ' MAKE SURE WE HAVE arrStack(0) WHICH HOLDS INDEX OF NEXT EMPTY NODE
  635.         If UBound(arrStack) < 0 Then
  636.             ' IF NOT FOUND THEN ADD IT
  637.             ReDim arrStack(0) As StackNodeType
  638.             arrStack(0).NextIndex = 0 ' 0 means no empty notes to reuse
  639.         End If
  640.  
  641.         ' ANY EMPTY?
  642.         If arrStack(0).NextIndex > 0 Then
  643.             ' reuse the empty one
  644.             NextIndex = arrStack(0).NextIndex
  645.  
  646.             ' move next empty to next available
  647.             arrStack(0).NextIndex = arrStack(NextIndex).NextIndex
  648.         Else
  649.             ' no empty, add a node
  650.             ReDim _Preserve arrStack(UBound(arrStack) + 1) As StackNodeType
  651.             NextIndex = UBound(arrStack)
  652.         End If
  653.  
  654.     End If
  655.  
  656.     ' ADD VALUES
  657.     If NextIndex > 0 Then
  658.         arrStack(NextIndex).NextIndex = NextAfterIndex
  659.         arrStack(NextIndex).name = sName
  660.         arrStack(NextIndex).value = iValue
  661.     End If
  662.  
  663.     ' RETURN RESULT (ADDED INDEX, OR 0 FOR NONE/ERROR)
  664.     DoPush5% = NextIndex
  665. End Function ' DoPush5%
  666.  
  667. ' /////////////////////////////////////////////////////////////////////////////
  668. ' RECEIVES:
  669. ' arrStack = array of nodes representing multiple stacks
  670. ' MyIndex = node to retrieve
  671.  
  672. ' RETURNS:
  673. ' NextAfterIndex = index of new top node in the chain
  674. ' (0 indicates none left, -1 indicates MyIndex not found)
  675. ' (arrStack is updated by reference)
  676.  
  677. Function DoPop5% (arrStack() As StackNodeType, MyIndex As Integer)
  678.     Dim NewTop As Integer
  679.     'Dim NextIndex As Integer
  680.  
  681.     NewTop = 0
  682.     If MyIndex > 0 Then
  683.         If UBound(arrStack) >= MyIndex Then
  684.             ' return the new top node (0 means none left)
  685.             NewTop = arrStack(MyIndex).NextIndex
  686.  
  687.             ' do we have any deleted nodes?
  688.             If arrStack(0).NextIndex > 0 Then
  689.                 ' insert current node at the top of the "deleted" chain
  690.                 arrStack(MyIndex).NextIndex = arrStack(0).NextIndex
  691.             End If
  692.  
  693.             ' soft delete
  694.             arrStack(0).NextIndex = MyIndex
  695.         Else
  696.             NewTop = -1
  697.         End If
  698.     Else
  699.         NewTop = -1
  700.     End If
  701.  
  702.     'print "removed " + cstr$(MyIndex) + ", returned " + cstr$(NewTop)
  703.     DoPop5% = NewTop
  704. End Function ' DoPop5%
  705.  
  706. ' /////////////////////////////////////////////////////////////////////////////
  707. ' RECEIVES:
  708. ' arrStack = array of nodes representing multiple stacks
  709. ' MyIndex = first node in chain
  710. '
  711. ' RETURNS:
  712. ' NextAfterIndex = index of new top node in the chain
  713. ' (0 indicates none left, -1 indicates MyIndex not found)
  714. ' (arrStack is updated by reference)
  715.  
  716. Function DumpStack5$ (arrStack() As StackNodeType, MyIndex1 As Integer, sHeadingText As String)
  717.     Dim sResult As String
  718.     Dim MyIndex As Integer
  719.     Dim sNext As String
  720.     Dim sLine As String
  721.     Dim iPrintCount As Integer
  722.     Dim bContinue As Integer
  723.  
  724.     sResult = ""
  725.     MyIndex = MyIndex1 ' prevents any updates of parameter
  726.     bContinue = TRUE
  727.  
  728.     sLine = sHeadingText
  729.     iPrintCount = 0
  730.     Do While bContinue
  731.         If UBound(arrStack) >= MyIndex Then
  732.             ' WRAP OUTPUT TO 80 COLUMNS
  733.             If Len(sLine) > 64 Then
  734.                 sResult = sResult + IIFSTR$(Len(sResult) = 0, "", Chr$(10)) + "    " + sLine
  735.                 sLine = ""
  736.                 iPrintCount = 0
  737.             End If
  738.  
  739.             sLine = sLine + IIFSTR$(iPrintCount = 0, "", ", ")
  740.             sLine = sLine + cstr$(MyIndex) + "=" + arrStack(MyIndex).name + ">"
  741.             sLine = sLine + cstr$(arrStack(MyIndex).NextIndex)
  742.  
  743.             iPrintCount = iPrintCount + 1
  744.             MyIndex = arrStack(MyIndex).NextIndex
  745.  
  746.             ' QUIT WHEN WE REACH NextIndex = 0
  747.             If MyIndex < 1 Then
  748.                 bContinue = FALSE
  749.             End If
  750.         Else
  751.             Exit Do
  752.         End If
  753.     Loop
  754.     sResult = sResult + IIFSTR$(Len(sResult) = 0, "", Chr$(10)) + "    " + sLine
  755.  
  756.     DumpStack5$ = sResult
  757. End Function ' DumpStack5$
  758.  
  759. ' /////////////////////////////////////////////////////////////////////////////
  760. ' RECEIVES:
  761. ' arrStack = array of nodes representing multiple stacks
  762. ' MyIndex = first node in chain
  763. '
  764. ' RETURNS:
  765. ' NextAfterIndex = index of new top node in the chain
  766. ' (0 indicates none left, -1 indicates MyIndex not found)
  767. ' (arrStack is updated by reference)
  768.  
  769. Function DumpStack5b$ (arrStack() As StackNodeType, MyIndex1 As Integer)
  770.     Dim sResult As String
  771.     Dim MyIndex As Integer
  772.     Dim sNext As String
  773.  
  774.     sResult = ""
  775.     MyIndex = MyIndex1 ' prevents any updates of parameter
  776.  
  777.     Do While MyIndex > 0
  778.         If UBound(arrStack) >= MyIndex Then
  779.             sNext = ""
  780.             sNext = sNext + cstr$(MyIndex) + "("
  781.             sNext = sNext + "name=" + Chr$(34) + arrStack(MyIndex).name + Chr$(34)
  782.             sNext = sNext + ", "
  783.             sNext = sNext + "value=" + cstr$(arrStack(MyIndex).value)
  784.             sNext = sNext + ", "
  785.             sNext = sNext + "nextIndex=" + cstr$(arrStack(MyIndex).NextIndex)
  786.             sNext = sNext + ")"
  787.  
  788.             sResult = sResult + IIFSTR$(Len(sResult) = 0, "", Chr$(10))
  789.             sResult = sResult + "    " + sNext
  790.  
  791.             MyIndex = arrStack(MyIndex).NextIndex
  792.         Else
  793.             Exit Do
  794.         End If
  795.     Loop
  796.  
  797.     DumpStack5b$ = sResult
  798. End Function ' DumpStack5b$
  799.  
  800. ' /////////////////////////////////////////////////////////////////////////////
  801. ' Convert a value to string and trim it (because normal Str$ adds spaces)
  802.  
  803. Function cstr$ (myValue)
  804.     'cstr$ = LTRIM$(RTRIM$(STR$(myValue)))
  805.     cstr$ = _Trim$(Str$(myValue))
  806. End Function ' cstr$
  807.  
  808. ' /////////////////////////////////////////////////////////////////////////////
  809.  
  810. Function IIF (Condition, IfTrue, IfFalse)
  811.     If Condition Then IIF = IfTrue Else IIF = IfFalse
  812.  
  813. ' /////////////////////////////////////////////////////////////////////////////
  814.  
  815. Function IIFSTR$ (Condition, IfTrue$, IfFalse$)
  816.     If Condition Then IIFSTR$ = IfTrue$ Else IIFSTR$ = IfFalse$
  817.  
  818. ' /////////////////////////////////////////////////////////////////////////////
  819. ' Generate random value between Min and Max.
  820. Function RandomNumber% (Min%, Max%)
  821.     Dim NumSpread%
  822.  
  823.     ' SET RANDOM SEED
  824.     'Randomize ' Initialize random-number generator.
  825.  
  826.     ' GET RANDOM # Min%-Max%
  827.     'RandomNumber = Int((Max * Rnd) + Min) ' generate number
  828.  
  829.     NumSpread% = (Max% - Min%) + 1
  830.  
  831.     RandomNumber% = Int(Rnd * NumSpread%) + Min% ' GET RANDOM # BETWEEN Max% AND Min%
  832.  
  833. End Function ' RandomNumber%
  834.  
  835. ' /////////////////////////////////////////////////////////////////////////////
  836. ' FROM: String Manipulation
  837. ' http://www.[abandoned, outdated and now likely malicious qb64 dot net website - don’t go there]/forum/index_topic_5964-0/
  838. '
  839. 'SUMMARY:
  840. '   Purpose:  A library of custom functions that transform strings.
  841. '   Author:   Dustinian Camburides (dustinian@gmail.com)
  842. '   Platform: QB64 (www.[abandoned, outdated and now likely malicious qb64 dot net website - don’t go there])
  843. '   Revision: 1.6
  844. '   Updated:  5/28/2012
  845.  
  846. 'SUMMARY:
  847. '[Replace$] replaces all instances of the [Find] sub-string with the [Add] sub-string within the [Text] string.
  848. 'INPUT:
  849. 'Text: The input string; the text that's being manipulated.
  850. 'Find: The specified sub-string; the string sought within the [Text] string.
  851. 'Add: The sub-string that's being added to the [Text] string.
  852.  
  853. Function Replace$ (Text1 As String, Find1 As String, Add1 As String)
  854.     ' VARIABLES:
  855.     Dim Text2 As String
  856.     Dim Find2 As String
  857.     Dim Add2 As String
  858.     Dim lngLocation As Long ' The address of the [Find] substring within the [Text] string.
  859.     Dim strBefore As String ' The characters before the string to be replaced.
  860.     Dim strAfter As String ' The characters after the string to be replaced.
  861.  
  862.     ' INITIALIZE:
  863.     ' MAKE COPIESSO THE ORIGINAL IS NOT MODIFIED (LIKE ByVal IN VBA)
  864.     Text2 = Text1
  865.     Find2 = Find1
  866.     Add2 = Add1
  867.  
  868.     lngLocation = InStr(1, Text2, Find2)
  869.  
  870.     ' PROCESSING:
  871.     ' While [Find2] appears in [Text2]...
  872.     While lngLocation
  873.         ' Extract all Text2 before the [Find2] substring:
  874.         strBefore = Left$(Text2, lngLocation - 1)
  875.  
  876.         ' Extract all text after the [Find2] substring:
  877.         strAfter = Right$(Text2, ((Len(Text2) - (lngLocation + Len(Find2) - 1))))
  878.  
  879.         ' Return the substring:
  880.         Text2 = strBefore + Add2 + strAfter
  881.  
  882.         ' Locate the next instance of [Find2]:
  883.         lngLocation = InStr(1, Text2, Find2)
  884.  
  885.         ' Next instance of [Find2]...
  886.     Wend
  887.  
  888.     ' OUTPUT:
  889.     Replace$ = Text2
  890. End Function ' Replace$
  891.  
« Last Edit: July 01, 2021, 09:41:17 am by madscijr »