' OPTIONS
' GLOBAL DECLARATIONS a$=string, i%=integer, L&=long, s!=single, d#=double
' FOR Petr's suggestion #2:
' FOR ArrayOfStacks4
Type entity_spec_structure
i
As Integer ' index from entity_spec() represented by this entity
' FOR ArrayOfStacks5
'IsDeleted AS INTEGER <- not needed but you can add this if you want a way to quickly test if a node is deleted
'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
'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
' INITIALIZE
' RUN THE MAIN PROGRAM
main
' FINISH
System ' return control to the operating system Print ProgramName$
+ " finished."
' /////////////////////////////////////////////////////////////////////////////
Print "Test different ways to implement an array of stacks" Print "1. Method #1 = fixed length string" Print "2. Method #2 = user defined type" Print "3. Method #3 = simple string-based stack" Print "4. Method #4 = entity arrays(?) = not working" Print "5. Method #5 = array-based stack of objects (UDTs)" Print "What to do ('q' to exit)"
ArrayOfStacks1
ArrayOfStacks2
ArrayOfStacks3
Print "Under construction"
ArrayOfStacks5
Print "Under construction"
' /////////////////////////////////////////////////////////////////////////////
' Re: good way to implement an array of stacks/linked lists (for 2D tile-based game)?
' https://www.qb64.org/forum/index.php?topic=4008.0
' SMcNeill
' « Reply #2 on: Today at 03:28:41 PM »
' Wouldn't the easiest way to do something like this be just:
'REDIM Objects(x_tile_limit, y_tile_limit) AS STRING * 10 'for a maximum of 10 items per tile
ReDim Objects
(x_tile_limit
, y_tile_limit
) As String * z_stack_limit
' for a maximum of z_stack_limit items per tile
Objects
(10, 10) = Chr$(1) ' Item 1 is on the bottom of the stack Objects
(10, 10) = Objects
(10, 10) + Chr$(23) ' Item 23 is on top of it Objects
(10, 10) = Objects
(10, 10) + Chr$(107) ' Item 107 is above that one Objects
(10, 10) = Objects
(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 xPos
= 0 To 2 ' x_tile_limit For yPos
= 0 To 2 ' y_tile_limit Print "(" + cstr$
(xPos
) + "," + cstr$
(yPos
) + ")" For zPos
= 1 To z_stack_limit
' Do whatever you do with that object
Print " " + cstr$
(zPos
) + " = " + Chr$(34) + Mid$(Objects
(xPos
, yPos
), zPos
, 1) + Chr$(34) 'NEXT z, y, x
Input "Press <ENTER> to continue"; in$
' /////////////////////////////////////////////////////////////////////////////
' Re: good way to implement an array of stacks/linked lists (for 2D tile-based game)?
' https://www.qb64.org/forum/index.php?topic=4008.0
' Petr
' « Reply #3 on: Today at 03:34:47 PM »
' Easiest it is using TYPE. Just create usertype field structure. Something as this:
' fill grid X and Y positions
UserF(xPos, yPos).X_POS = xPos
UserF(xPos, yPos).Y_POS = yPos
UserF
(xPos
, yPos
).Grid.texture
= Rnd * 1000 ' _LOADIMAGE.... UserF
(xPos
, yPos
).Grid.Dynamic_Value
= Rnd * 75 ' _SNDOPEN.....
Print "(" + cstr$
(xPos
) + "," + cstr$
(yPos
) + ")" Print " .X_POS=" + cstr$
(UserF
(xPos
, yPos
).X_POS
) Print " .Y_POS=" + cstr$
(UserF
(xPos
, yPos
).X_POS
) Print " .Grid.texture=" + cstr$
(UserF
(xPos
, yPos
).Grid.texture
) Print " .Grid.Dynamic_Value=" + cstr$
(UserF
(xPos
, yPos
).Grid.Dynamic_Value
)
Input "Press <ENTER> to continue"; in$
' /////////////////////////////////////////////////////////////////////////////
' Simplest approach where each tile is limited to the size of the string,
' to get the number of objects on a tile, do
' iNumObjects = LEN(Objects(xPos, yPos)),
' POPULATE MAP WITH A RANDOM # OF OBJECTS ON EACH TILE
' USING "PUSH" ROUTINE
iCount = RandomNumber%(1, 20)
iChar = RandomNumber%(65, 90)
DoPush3 arrMap
(), xPos
, yPos
, Chr$(iChar
) arrMap(xPos, yPos) = ""
' PRINT THE INITIAL MAP
Print "CONTENTS BEFORE REMOVE:" sLine = ""
' WRAP OUTPUT TO 80 COLUMNS
' MAX SIZE EACH ENTRY = 16: (10,10)="ABCDE"
' 1234567890123456
sLine = ""
sLine
= sLine
+ "(" + cstr$
(xPos
) + "," + cstr$
(yPos
) + ")=" + Chr$(34) + arrMap
(xPos
, yPos
) + Chr$(34) + " " Input "Press <ENTER> to continue"; in$
' REMOVE ONE OBJECT OFF THE TOP OF EACH TILE
' USING "POP" FUNCTION
Print "REMOVED ONE FROM THE TOP OF EACH:" sRemoved = ""
sLine = ""
sLine = ""
sNext = DoPop3$(arrMap(), xPos, yPos)
sRemoved = sRemoved + sNext
sLine
= sLine
+ "(" + cstr$
(xPos
) + "," + cstr$
(yPos
) + ")=" + Chr$(34) + arrMap
(xPos
, yPos
) + Chr$(34) + " "
sLine = ""
sLine = ""
sLine
= sLine
+ IIFSTR$
(iLoop
> 1, ", ", "") + Mid$(sRemoved
, iLoop
, 1) Input "Press <ENTER> to continue"; in$
' /////////////////////////////////////////////////////////////////////////////
arrTiles(xPos, yPos) = arrTiles(xPos, yPos) + sObject
' /////////////////////////////////////////////////////////////////////////////
iLen
= Len(arrTiles
(xPos
, yPos
)) DoPop3$ = arrTiles(xPos, yPos)
arrTiles(xPos, yPos) = ""
DoPop3$
= Right$(arrTiles
(xPos
, yPos
), 1) arrTiles
(xPos
, yPos
) = Left$(arrTiles
(xPos
, yPos
), Len(arrTiles
(xPos
, yPos
)) - 1) DoPop3$ = ""
' /////////////////////////////////////////////////////////////////////////////
sLine = ""
sLine = ""
sLine
= sLine
+ cstr$
(iLoop
) + "=" + Chr$(34) + Chr$(iLoop
) + Chr$(34) + IIFSTR$
(iLoop
< 127, ",", "")
Input "Press <ENTER> to continue"; in$
' /////////////////////////////////////////////////////////////////////////////
' Re: good way to implement an array of stacks/linked lists (for 2D tile-based game)?
' https://www.qb64.org/forum/index.php?topic=4008.0
' johannhowitzer
' « Reply #9 on: Yesterday at 10:46:48 PM »
' 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:
'
' 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:
'
' 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.
' COMMENTS:
' This seems overly complicated. Do we really need entity(100)?
Dim map_objects
(10, 10, 10) As Integer ' First two dimensions are coordinates, last carries the stack, value is the entity index Dim entity_spec
(100) As entity_spec_structure
' preset data for entities that can be on tiles in your map Dim entity
(100) As entity_structure
' Wipe the array first
For x
= 1 To 10 ' [map width] For y
= 1 To 10 ' [map height] For n
= 1 To 10 ' [tile object limit] map_objects(x, y, n) = FALSE
' Put each entity on its tile
entity_count = 10 ' we will drop 10 of these
n = 0
For e
= 1 To entity_count
' entity_spec_structure values:
entity_spec
(e
).
name = "entity #" + cstr$
(e
) entity_spec(e).value = RandomNumber%(0, 100)
' entity_structure values:
n = n + 1
x = RandomNumber%(1, 10)
y = RandomNumber%(1, 10)
entity(n).i = e
entity(n).x = x
entity(n).y = y
' add to map:
map_objects(entity(n).x, entity(n).y, n) = e
' Show results
For y
= 1 To 10 ' [map height] For x
= 1 To 10 ' [map width] For n
= 1 To 10 ' [tile object limit] If map_objects
(x
, y
, n
) <> FALSE
Then e = map_objects(entity(n).x, entity(n).y, n)
Input "Press <ENTER> to continue"; in$
' /////////////////////////////////////////////////////////////////////////////
ReDim arrStack
(0) As StackNodeType
' index 0 holds next empty/deleted node
' INITIALIZE STACK
arrStack(0).NextIndex = 0 ' values > -1 points to next available node
arrStack
(0).
name = "NextFree"
' POPULATE MAP WITH A RANDOM # OF OBJECTS ON EACH TILE
' USING "PUSH" ROUTINE
iTotal = 0
iCount = RandomNumber%(1, 20)
MyIndex = 0
iTotal = iTotal + 1
sName = "item" + cstr$(iTotal)
iValue = RandomNumber%(0, 100)
MyIndex = DoPush5%(arrStack(), MyIndex, sName, iValue)
arrMap(xPos, yPos) = MyIndex
arrMap(xPos, yPos) = 0
' PRINT THE INITIAL MAP
Print "CONTENTS BEFORE REMOVE:" If arrMap
(xPos
, yPos
) > 0 Then MyIndex = arrMap(xPos, yPos)
Print DumpStack5$
(arrStack
(), MyIndex
, "arrMap(" + cstr$
(yPos
) + "," + cstr$
(xPos
) + "): ") Print DumpStack5$
(arrStack
(), 0, "Next Free: ") Input "Press <ENTER> to continue"; in$
' REMOVE ONE OBJECT OFF THE TOP OF EACH TILE
' USING "POP" FUNCTION
sChanged = ""
sLine = ""
iPrintCount = 0
' WRAP OUTPUT TO 80 COLUMNS
sChanged
= sChanged
+ IIFSTR$
(Len(sChanged
) = 0, "", Chr$(10)) + " " + sLine
sLine = ""
iPrintCount = 0
If arrMap
(xPos
, yPos
) > 0 Then MyIndex = arrMap(xPos, yPos)
iPrintCount = iPrintCount + 1
sLine = sLine + IIFSTR$(iPrintCount > 1, ", ", "")
sLine
= sLine
+ cstr$
(MyIndex
) + "=" + arrStack
(MyIndex
).
name + ">" sLine = sLine + cstr$(arrStack(MyIndex).NextIndex)
NewTop = DoPop5%(arrStack(), MyIndex)
arrMap(xPos, yPos) = NewTop
sChanged
= sChanged
+ IIFSTR$
(Len(sChanged
) = 0, "", Chr$(10)) + " " + sLine
Print "REMOVED ONE FROM THE TOP OF EACH:" Input "Press <ENTER> to continue"; in$
' PRINT MAP AFTER ITEMS REMOVED
Print "CONTENTS AFTER REMOVE:" If arrMap
(xPos
, yPos
) > 0 Then Print DumpStack5$
(arrStack
(), arrMap
(xPos
, yPos
), "arrMap(" + cstr$
(yPos
) + "," + cstr$
(xPos
) + "): ") Print DumpStack5$
(arrStack
(), 0, "Next Free: ") Input "Press <ENTER> to continue"; in$
' ADD ONE OBJECT TO THE TOP OF EACH TILE
' USING "PUSH" FUNCTION
sChanged = ""
sLine = ""
' WRAP OUTPUT TO 80 COLUMNS
sChanged
= sChanged
+ IIFSTR$
(Len(sChanged
) = 0, "", Chr$(10)) + " " + sLine
sLine = ""
If arrMap
(xPos
, yPos
) > 0 Then MyIndex = arrMap(xPos, yPos)
iTotal = iTotal + 1
sName = "item" + cstr$(iTotal)
iValue = RandomNumber%(0, 100)
MyIndex = DoPush5%(arrStack(), MyIndex, sName, iValue)
arrMap(xPos, yPos) = MyIndex
sLine
= sLine
+ cstr$
(MyIndex
) + "=" + arrStack
(MyIndex
).
name + ">" sLine = sLine + cstr$(arrStack(MyIndex).NextIndex) + ", "
sChanged
= sChanged
+ IIFSTR$
(Len(sChanged
) = 0, "", Chr$(10)) + " " + sLine
Print "ADDED ONE TO THE TOP OF EACH:" Input "Press <ENTER> to continue"; in$
' PRINT MAP AFTER ITEMS ADDED
Print "CONTENTS AFTER ADDING:" If arrMap
(xPos
, yPos
) > 0 Then Print DumpStack5$
(arrStack
(), arrMap
(xPos
, yPos
), "arrMap(" + cstr$
(yPos
) + "," + cstr$
(xPos
) + "): ") Print DumpStack5$
(arrStack
(), 0, "Next Free: ") Input "Press <ENTER> to continue"; in$
' /////////////////////////////////////////////////////////////////////////////
' RECEIVES:
' arrStack = array of nodes representing multiple stacks
' MyIndex = node to drop new element on top of
' sName = string value for new node
' iValue = numeric value for new node
' RETURNS:
' NextIndex = the new node's index in array arrStack
' (arrStack is updated by reference)
' HOW THE STACK WORKS:
'
' arrStack() holds multiple stacks, each "chain" has 1 or more node,
' where the top node's index is kept track of by whatever code is using this,
' for example a 2-D tilemap might holds the index of the top object at arrMap(x,y)
'
' Index 0 is used to track deleted nodes,
' because we don't delete nodes from the arrStack, just reuse them.
' When a node is "deleted", we set arrStack(0) to the index of the deleted node.
' When we delete subsequent nodes, each one's .nextIndex points to the next available
' deleted node in a "chain". When we reuse the next given node,
' we repoint arrStack(0) to the next one, and so arrStack(0).nextIndex always points
' to the next available node to be reused.
'
' If arrStack(0) = 0, there are no deleted nodes to be reused,
' then we append a new element to the array with REDIM _PRESERVE.
'
' If MyIndex = 0 then a new chain is created,
' else the new value is added to the top of item MyIndex,
' and the new item's .nextIndex is set to point to MyIndex,
' so we have arrStack(n).nextIndex always points to the next item in that item's chain.
' arrStack(0) is the head of the deleted/to be reused chain.
NextIndex = 0
' MAKE SURE WE HAVE THE ITEM MyIndex TO DROP ON TOP OF
' DROP ON TOP OF ITEM MyIndex
' ADD NEW ITEM TO TOP OF CHAIN MyIndex
NextAfterIndex = MyIndex
' ANY EMPTY?
If arrStack
(0).NextIndex
> 0 Then ' reuse the empty one
NextIndex = arrStack(0).NextIndex
' move next empty to next available
arrStack(0).NextIndex = arrStack(NextIndex).NextIndex
' no empty, add a node
' (ITEM MyIndex NOT FOUND, RETURN 0 INDICATING ERROR)
' NOTHING TO DROP ON TOP OF, ADD TO A NEW CHAIN
NextAfterIndex = 0 ' this is the first one, no more
' MAKE SURE WE HAVE arrStack(0) WHICH HOLDS INDEX OF NEXT EMPTY NODE
' IF NOT FOUND THEN ADD IT
arrStack(0).NextIndex = 0 ' 0 means no empty notes to reuse
' ANY EMPTY?
If arrStack
(0).NextIndex
> 0 Then ' reuse the empty one
NextIndex = arrStack(0).NextIndex
' move next empty to next available
arrStack(0).NextIndex = arrStack(NextIndex).NextIndex
' no empty, add a node
' ADD VALUES
arrStack(NextIndex).NextIndex = NextAfterIndex
arrStack
(NextIndex
).
name = sName
arrStack(NextIndex).value = iValue
' RETURN RESULT (ADDED INDEX, OR 0 FOR NONE/ERROR)
DoPush5% = NextIndex
' /////////////////////////////////////////////////////////////////////////////
' RECEIVES:
' arrStack = array of nodes representing multiple stacks
' MyIndex = node to retrieve
' RETURNS:
' NextAfterIndex = index of new top node in the chain
' (0 indicates none left, -1 indicates MyIndex not found)
' (arrStack is updated by reference)
'Dim NextIndex As Integer
NewTop = 0
' return the new top node (0 means none left)
NewTop = arrStack(MyIndex).NextIndex
' do we have any deleted nodes?
If arrStack
(0).NextIndex
> 0 Then ' insert current node at the top of the "deleted" chain
arrStack(MyIndex).NextIndex = arrStack(0).NextIndex
' soft delete
arrStack(0).NextIndex = MyIndex
NewTop = -1
NewTop = -1
'print "removed " + cstr$(MyIndex) + ", returned " + cstr$(NewTop)
DoPop5% = NewTop
' /////////////////////////////////////////////////////////////////////////////
' RECEIVES:
' arrStack = array of nodes representing multiple stacks
' MyIndex = first node in chain
'
' RETURNS:
' NextAfterIndex = index of new top node in the chain
' (0 indicates none left, -1 indicates MyIndex not found)
' (arrStack is updated by reference)
sResult = ""
MyIndex = MyIndex1 ' prevents any updates of parameter
bContinue = TRUE
sLine = sHeadingText
iPrintCount = 0
' WRAP OUTPUT TO 80 COLUMNS
sResult
= sResult
+ IIFSTR$
(Len(sResult
) = 0, "", Chr$(10)) + " " + sLine
sLine = ""
iPrintCount = 0
sLine = sLine + IIFSTR$(iPrintCount = 0, "", ", ")
sLine
= sLine
+ cstr$
(MyIndex
) + "=" + arrStack
(MyIndex
).
name + ">" sLine = sLine + cstr$(arrStack(MyIndex).NextIndex)
iPrintCount = iPrintCount + 1
MyIndex = arrStack(MyIndex).NextIndex
' QUIT WHEN WE REACH NextIndex = 0
bContinue = FALSE
sResult
= sResult
+ IIFSTR$
(Len(sResult
) = 0, "", Chr$(10)) + " " + sLine
DumpStack5$ = sResult
' /////////////////////////////////////////////////////////////////////////////
' RECEIVES:
' arrStack = array of nodes representing multiple stacks
' MyIndex = first node in chain
'
' RETURNS:
' NextAfterIndex = index of new top node in the chain
' (0 indicates none left, -1 indicates MyIndex not found)
' (arrStack is updated by reference)
sResult = ""
MyIndex = MyIndex1 ' prevents any updates of parameter
sNext = ""
sNext = sNext + cstr$(MyIndex) + "("
sNext
= sNext
+ "name=" + Chr$(34) + arrStack
(MyIndex
).
name + Chr$(34) sNext = sNext + ", "
sNext = sNext + "value=" + cstr$(arrStack(MyIndex).value)
sNext = sNext + ", "
sNext = sNext + "nextIndex=" + cstr$(arrStack(MyIndex).NextIndex)
sNext = sNext + ")"
sResult
= sResult
+ IIFSTR$
(Len(sResult
) = 0, "", Chr$(10)) sResult = sResult + " " + sNext
MyIndex = arrStack(MyIndex).NextIndex
DumpStack5b$ = sResult
' /////////////////////////////////////////////////////////////////////////////
' Convert a value to string and trim it (because normal Str$ adds spaces)
'cstr$ = LTRIM$(RTRIM$(STR$(myValue)))
' /////////////////////////////////////////////////////////////////////////////
Function IIF
(Condition
, IfTrue
, IfFalse
)
' /////////////////////////////////////////////////////////////////////////////
Function IIFSTR$
(Condition
, IfTrue$
, IfFalse$
) If Condition
Then IIFSTR$
= IfTrue$
Else IIFSTR$
= IfFalse$
' /////////////////////////////////////////////////////////////////////////////
' Generate random value between Min and Max.
' SET RANDOM SEED
'Randomize ' Initialize random-number generator.
' GET RANDOM # Min%-Max%
'RandomNumber = Int((Max * Rnd) + Min) ' generate number
NumSpread% = (Max% - Min%) + 1
RandomNumber%
= Int(Rnd * NumSpread%
) + Min%
' GET RANDOM # BETWEEN Max% AND Min%
' /////////////////////////////////////////////////////////////////////////////
' FROM: String Manipulation
' http://www.[abandoned, outdated and now likely malicious qb64 dot net website - don’t go there]/forum/index_topic_5964-0/
'
'SUMMARY:
' Purpose: A library of custom functions that transform strings.
' Author: Dustinian Camburides (dustinian@gmail.com)
' Platform: QB64 (www.[abandoned, outdated and now likely malicious qb64 dot net website - don’t go there])
' Revision: 1.6
' Updated: 5/28/2012
'SUMMARY:
'[Replace$] replaces all instances of the [Find] sub-string with the [Add] sub-string within the [Text] string.
'INPUT:
'Text: The input string; the text that's being manipulated.
'Find: The specified sub-string; the string sought within the [Text] string.
'Add: The sub-string that's being added to the [Text] string.
' VARIABLES:
Dim lngLocation
As Long ' The address of the [Find] substring within the [Text] string. Dim strBefore
As String ' The characters before the string to be replaced. Dim strAfter
As String ' The characters after the string to be replaced.
' INITIALIZE:
' MAKE COPIESSO THE ORIGINAL IS NOT MODIFIED (LIKE ByVal IN VBA)
Text2 = Text1
Find2 = Find1
Add2 = Add1
lngLocation
= InStr(1, Text2
, Find2
)
' PROCESSING:
' While [Find2] appears in [Text2]...
' Extract all Text2 before the [Find2] substring:
strBefore
= Left$(Text2
, lngLocation
- 1)
' Extract all text after the [Find2] substring:
strAfter
= Right$(Text2
, ((Len(Text2
) - (lngLocation
+ Len(Find2
) - 1))))
' Return the substring:
Text2 = strBefore + Add2 + strAfter
' Locate the next instance of [Find2]:
lngLocation
= InStr(1, Text2
, Find2
)
' Next instance of [Find2]...
' OUTPUT:
Replace$ = Text2