Hello. I am trying to make a "finding opponent" system like in Clash of Clans, where you find another player's base who has around the same amount of trophies for you. The trick is that the game needs to find someone who is around the same amount of trophies as the player. All the trophies are stored in a DataStore. All I need to do is to somehow find another player from the DataStore that is around the same amount of trophies as the player.
I honestly have no idea about how to solve this problem, any help would be greatly appreciated!
I accidentally looked at the OrderedDataStore documentation and found my solution, the 2 last properties let you specify the minimum and maximum value you are going to look for in a data store, you can use this to only pick values inside of a specified range.
For cases where Bob has 100000 trophies but you have 100 trophies the range differs too much so it can't be a known value, instead there is a default range value, if no people in that range is found the range is multiplied by 2 and checks again, for example if with numbers 100000 and 100 the base range is 5000, at first it won't find anything, then it will multiply the range by 2 which will give you 10000, still nothing, it will continue multiplying it -> 20000 -> 40000 -> 80000 -> 160000 and when it comes to 160000, 100000 will be in range, this can require a lot of requests to the data store if the difference between users is huge, you can solve that using task.wait
, I did not do that since yes.
Here is a module that I wrote for that:
local searchlib = {} local DataStoreService = game:GetService("DataStoreService") local data_store function searchlib.init_data_store(name) data_store = DataStoreService:GetOrderedDataStore(name) end function searchlib.add_count(key, count) local success, response = pcall(data_store.SetAsync, data_store, key, count) if not success then warn("Failed to add count:", response) end end function searchlib.get_count(key) -- Does not include pcall cuz lazy >wO return data_store:GetAsync(key) end function searchlib.get_closest_count(count, required, range) local closest = {} local found = 0 while true do -- Neither does include pcall do yourself noob local pages = data_store:GetSortedAsync(false, 50, count - range, count + range) while true do local page = pages:GetCurrentPage() if #page == 0 then break end for _, save in pairs(page) do if closest[save.key] then continue end closest[save.key] = save.value found += 1 if found >= required then return closest end end if pages.IsFinished then break end page:AdvanceNextPageAsync() end range *= 2 end return closest end return searchlib
And here it is used by a server script for test cases:
local searchlib = require(script.searchlib) local players = { ["bob"] = 100000, ["me"] = 20, ["yapelize"] = -100, ["feahren"] = 5000, ["fish"] = 8000, ["opalmime"] = 10000, ["weeb"] = 8888888, ["iiilllii"] = 69, ["ruby"] = 1, ["kirda"] = 1013 } searchlib.init_data_store("test") for player, trophies in pairs(players) do searchlib.add_count(player, trophies) end print( "closest for 5000 trophies:", searchlib.get_closest_count(5000, 4, 500), "closest for 100000 trophies:", searchlib.get_closest_count(100000, 4, 10000), "closest for -2000 trophies:", searchlib.get_closest_count(-2000, 4, 300) )
My output after running the code was the following:
closest for 5000 trophies: ? { ["feahren"] = 5000, ["fish"] = 8000, ["kirda"] = 1013, ["opalmime"] = 10000 } closest for 100000 trophies: ? { ["bob"] = 100000, ["feahren"] = 5000, ["fish"] = 8000, ["opalmime"] = 10000 } closest for -2000 trophies: ? { ["iiilllii"] = 69, ["me"] = 20, ["ruby"] = 1, ["yapelize"] = -100 }
The function get_closest_count
takes 3 arguments, the first one is amount of trophies you want to get closest matches for, the second is required amount of matches, it will continue looping until it finds this amount of matched opponents and the third is the base range, for bigger numbers it should be possibly bigger since there is less players with a lot of trophies.
init_data_store
function initializes the data store with your given name. This module lacks pcall
s almost everywhere since that would make it even longer.
If you want to use this with your data store without loosing your data you will need to set async all data from your normal data store into this ordered data store.