In the past, a couple of you have asked for user data which can be associated to sprite numbers which should save you the trouble of having to search for the matching sprite in your arrays and then accessing its data. Currently, AppGameKit doesn't have a direct solution for this so I've whipped up a Hash Table for you guys to store your sprite numbers along with associated user data.
For those who don't know what Hash Tables/Sets are -
wiki.
How it works
In a nutshell, you give add an entry into the Hash Table by providing a
unique key (such as a sprite number) along with user data. The Hash Table will place it into its internal array based in the key. Insertion should be very quick with expected time of O(1) - constant. Worst case it'll have to resize which involves copying the entries into the temporary array, resize the main array and then rehashing (not exactly the same as copying) the entries back into the main array. This isn't done very often however.
How to use it
AddIntegerToHashSet(key,usr) - this will add a key (ie sprite number) into the hashset associated with your user data (usr). In most cases, this will probably be the array index of your own array, of the sprite you've just inserted into the hash table.
e.g AddSpriteToInteger(Sprites[3].spr, 3)
GetUserDataFromHashSet(key) - retrieves the associated data based on the key. Using the example above, this would return 3. In most cases, this retrieval should be instant (or after searching a couple of items).
DeleteIntegerFromHashSet(key) - removes the key along with its associated data from the Hash Set. Most times it will be done in constant time but on rare occasions it may involve resizing which will take O(n) linear time.
Those are the functions of most importance. There are others which will allow you to print the details of the hash set and even write the whole thing to a file.
Testing has been minimal
Because I mainly use Tier 2, I don't really have any projects I can integrate this hash set into so it has not been tested on a large scale. I do however have a small demo:
rem
rem AGK Application
rem
#constant HASH_PRIME 20011
// NOTE: 0 is not a valid number for the hashet; 0 is treated as NULL
// HASH_HashSet type ************************************************************************
type HASH_HashSetObject
value as integer
usrData as integer
occupied as integer
endtype
// HASH_HashSet vars
// current size of HASH_HashSet
global HASH_HashSetSize as integer
HASH_HashSetSize = 20
// number of objects inside HASH_HashSet
global HASH_numObjects as integer
HASH_numObjects = 0
global HASH_scale as integer
HASH_scale = HASH_HashSetSize*10+1
global HASH_offset as integer
HASH_offset = HASH_HashSetSize/2
global HASH_memory_magnitude as integer
HASH_memory_magnitude = 2
// HASH_HashSet
Dim HASH_HashSet[HASH_HashSetSize] as HASH_HashSetObject
// reset the entire HASH_HashSet ie everything = 0
HASH_wipeHashSet()
// End of HashSet vars and initialisation ****************************************************************************
// HashSet Demo ******************************************************************************************************
type Sprite
x as integer
y as integer
spr as integer
red as integer
green as integer
blue as integer
endtype
// Array of red sprites
Dim Sprites[100] as Sprite
for n = 1 to 10
Sprites[n].x = random(1,getVirtualWidth()-10)
Sprites[n].y = random(1,getVirtualHeight()-10)
Sprites[n].spr = createSprite(0)
// add data to HASH_HashSet - unique sprite number, userData: in this case, the index of the individual sprite in the Sprites[] array
addIntegerToHashSet(Sprites[n].spr,n)
Sprites[n].red = random(1,255)
Sprites[n].green = random(1,255)
Sprites[n].blue = random(1,255)
setSpriteColor(Sprites[n].spr, Sprites[n].red, Sprites[n].green,Sprites[n].blue,255)
setSpritePosition(Sprites[n].spr, Sprites[n].x, Sprites[n].y)
setSpriteSize(Sprites[n].spr, 4,4)
next n
sprCounter = n
writeHashSetToFile()
hit = 0
rem A Wizard Did It!
do
printHashSetDetails()
// if the user has clicked on a sprite, print out the details - HASH_HashSet vs AGK
if getPointerState()
hit = GetSpriteHit(getPointerX(),getPointerY())
if hit
// get the user data from the hashset, in this case, the index of the individual sprite in the Sprites[] array
index = getUserDataFromHashset(hit)
print("Hash Spr: " + str(Sprites[index].spr) + " " + "AGK Spr: " + str(hit))
print("Hash red: " + str(Sprites[index].red) + " " + "AGK Red: " + str(getSpriteColorRed(hit)))
print("Hash green: " + str(Sprites[index].green) + " " + "AGK Red: " + str(getSpriteColorGreen(hit)))
print("Hash blue: " + str(Sprites[index].blue) + " " + "AGK Red: " + str(getSpriteColorBlue(hit)))
printHashSetObject(hit)
// if the spacebar is pressed, delete the sprite
if getRawKeyPressed(32)
deleteSprite(hit)
deleteIntegerFromHashSet(hit)
sprCounter = sprCounter - 1
endif
endif
else
// if the spacebar has been pressed (while the user isn't clicking) - create a sprite
if getRawKeyPressed(32) and hit = 0
// add a sprite
if sprCounter <= 100
Sprites[sprCounter].x = random(1,getVirtualWidth()-10)
Sprites[sprCounter].y = random(1,getVirtualHeight()-10)
Sprites[sprCounter].spr = createSprite(0)
// add data to HASH_HashSet - unique sprite number, userData
addIntegerToHashSet(Sprites[sprCounter].spr,sprCounter)
// colour and position the sprite
Sprites[sprCounter].red = random(1,255)
Sprites[sprCounter].green = random(1,255)
Sprites[sprCounter].blue = random(1,255)
setSpriteColor(Sprites[sprCounter].spr, Sprites[sprCounter].red, Sprites[sprCounter].green,Sprites[sprCounter].blue,255)
setSpritePosition(Sprites[sprCounter].spr, Sprites[sprCounter].x, Sprites[sprCounter].y)
setSpriteSize(Sprites[sprCounter].spr, 4,4)
sprCounter = sprCounter + 1
endif
endif
endif
print(str(sprCounter))
print("Hit: " + str(hit))
Sync()
loop
// end demo *****************************************************************************************************************************************
function getHashSetSize()
endfunction HASH_HashSetSize
function getNumberOfHashSetObjects()
endfunction HASH_numObjects
// fill the HASH_HashSet with 0s
function HASH_wipeHashSet()
for n = 0 to HASH_HashSetSize
HASH_HashSet[n].value = 0
HASH_HashSet[n].usrData = 0
HASH_HashSet[n].occupied = 0
next n
endfunction
// hash the key ie generate an index for the HASH_HashSet array based off the key
function hash(key as integer)
index as integer
index = mod( mod( key*HASH_scale + HASH_offset, HASH_PRIME), HASH_HashSetSize)
endfunction index
// resize the HASH_HashSet - O(n) time
function HASH_resizeHashSet()
Dim temp[HASH_numObjects] as HASH_HashSetObject
tempIndex = 0
// fill the temp array with the HASH_HashSet objects
for n = 0 to HASH_HashSetSize
if HASH_HashSet[n].occupied = 1
value = HASH_HashSet[n].value
temp[tempIndex].value = value
temp[tempIndex].usrData = HASH_HashSet[n].usrData
tempIndex = tempIndex + 1
endif
next n
HASH_HashSetSize = HASH_numObjects*(HASH_memory_magnitude+1)
HASH_offset = HASH_HashSetSize/2
// resize HASH_HashSet
Dim HASH_HashSet[HASH_HashSetSize] as HASH_HashSetObject
// wipe the HASH_HashSet
HASH_wipeHashSet()
// add the temporary values back into the HASH_HashSet
for n = 0 to HASH_numObjects
index = hash(temp[n].value)
if HASH_HashSet[index].occupied = 0
HASH_HashSet[index].value = temp[n].value
HASH_HashSet[index].usrData = temp[n].usrData
HASH_HashSet[index].occupied = 1
else
while HASH_HashSet[index].occupied = 1
index = index + 1
if index > HASH_HashSetSize then index = 0
endwhile
HASH_HashSet[index].value = temp[n].value
HASH_HashSet[index].usrData = temp[n].usrData
HASH_HashSet[index].occupied = 1
endif
next n
endfunction
// expected - O(1) time, worst case due to resizing is O(n)
function addIntegerToHashSet(key as integer, usr as integer)
// check HASH_HashSetSize
if HASH_numObjects >= (HASH_HashSetSize-1)/HASH_memory_magnitude
HASH_resizeHashSet()
endif
// increase number of objects
HASH_numObjects = HASH_numObjects + 1
// add the integer and user data to the HASH_HashSet
index = hash(key)
// check the currently hashed spot - if available add the object
if HASH_HashSet[index].occupied = 0
HASH_HashSet[index].value = key
HASH_HashSet[index].usrData = usr
HASH_HashSet[index].occupied = 1
else
// if not available, linearly probe until we find an available spot
while HASH_HashSet[index].occupied = 1
index = index + 1
if index > HASH_HashSetSize then index = 0
endwhile
HASH_HashSet[index].value = key
HASH_HashSet[index].usrData = usr
HASH_HashSet[index].occupied = 1
endif
endfunction
// retrieve user data from a key
// expected O(1) - worst case O(n)
function getUserDataFromHashSet(key as integer)
// add the integer and user data to the HASH_HashSet
index = hash(key)
counter = 0
while HASH_HashSet[index].value <> key and counter<=HASH_HashSetSize
index = index + 1
if index > HASH_HashSetSize then index = 0
counter = counter + 1
endwhile
if counter > HASH_HashSetSize
exitfunction 0
endif
usr = HASH_HashSet[index].usrData
endfunction usr
// remove integer and associated data from HASH_HashSet
function deleteIntegerFromHashSet(key as integer)
// get a hashed index
index = hash(key)
counter = 0
if HASH_HashSet[index].value = key
HASH_HashSet[index].value = 0
HASH_HashSet[index].usrData = 0
HASH_HashSet[index].occupied = 0
// decrease the number of objects
HASH_numObjects = HASH_numObjects-1
else
while HASH_HashSet[index].value <> key and counter<=HASH_HashSetSize
index = index + 1
if index >= HASH_HashSetSize then index = 0
counter = counter + 1
endwhile
if counter<HASH_HashSetSize
HASH_HashSet[index].value = 0
HASH_HashSet[index].usrData = 0
HASH_HashSet[index].occupied = 0
// decrease the number of objects
HASH_numObjects = HASH_numObjects-1
endif
endif
if HASH_numObjects <= HASH_HashSetSize/(HASH_memory_magnitude+2) and HASH_numObjects > 0
HASH_resizeHashSet()
endif
endfunction
// print the entire HASH_HashSet
function printHashSet()
print("HASH_HashSet Size: " + str(HASH_HashSetSize))
print("Number of Objects: " + str(HASH_numObjects))
for n = 1 to HASH_HashSetSize
print("[" + str(n) + "]: val=" + str(HASH_HashSet[n].value) + " usr=" + str(HASH_HashSet[n].usrData) + " occ=" + str(HASH_HashSet[n].occupied) + " hash=" + str(hash(HASH_HashSet[n].value)))
next n
endfunction
// print single HASH_HashSet object
function printHashSetObject(key as integer)
n = hash(key)
print("HashSet[" + str(n) + "]: val=" + str(HASH_HashSet[n].value) + " usr=" + str(HASH_HashSet[n].usrData) + " occ=" + str(HASH_HashSet[n].occupied) + " hash=" + str(hash(HASH_HashSet[n].value)))
endfunction
// write the HASH_HashSet to a file
function writeHashSetToFile()
file = openToWrite("HASH_HashSetLog.txt")
writeLine(file, "HASH_HashSet Size: " + str(HASH_HashSetSize))
writeLine(file, "Number of Objects: " + str(HASH_numObjects))
writeLine(file, "Magnitute: " + str(HASH_memory_magnitude))
writeLine(file, "HASH_scale: " + str(HASH_scale))
writeLine(file, "HASH_offset: " + str(HASH_offset))
writeLIne(file, "HASH_PRIME: " + str(HASH_PRIME))
for n = 1 to HASH_HashSetSize
writeLine(file,"[" + str(n) + "]: val=" + str(HASH_HashSet[n].value) + " usr=" + str(HASH_HashSet[n].usrData) + " occ=" + str(HASH_HashSet[n].occupied) + " hash=" + str(hash(HASH_HashSet[n].value)))
next n
closefile(file)
endfunction
function printHashSetDetails()
print("HASH_HashSet Size: " + str(HASH_HashSetSize))
print("Number of Objects: " + str(HASH_numObjects))
endfunction
Demo controls:
Add a sprite: spacebar
Delete a sprite: click on the sprite and press spacebar
Specifics
In this hashset I treat a key value of 0 as NULL. This means it's not a valid entry so undefined actions may occur if you attempt to add in key values of 0. I also have
not catered for
negative key values and this may cause "Out of bounds" errors so don't use them.
To use in your projects, place everything in-between the rows of stars (including the #constant and TYPE) at the top of your program.
This Hash Set uses linear probing as opposed to bucket lists since resizing multi-dimensional arrays are unpredictable in AGK.
To adjust the starting size of memory (>1 would be a good start), adjust the value of HASH_hashSetSize in its declaration.
After the HASH_HashSet has been declared the wipeHashSet() function must be called.
You can slightly control the size of the hash set by varying the HASH_memory_magnitude value. I recommend:
1: uses more processing but less memory
2: uses less processing but more memory
If you find any bugs, crashing etc, let me know and I'll try to fix it. Remember to back up your projects before you integrate this.
I hope this helps some of you out there.