Trying To Sort Players In A Table Based On The Amount Of Jewels They Have

I’ve been trying to sort players in a table in order from most gems to least. When there are 2 or more players print(jewels) prints 0 even when the player has more than 0 jewels. It works fine when there is only one player.

local plrs = {}
local playerRankingsDictionary = {}
local names = {}

local function SortByGemsDescending(plrs)
	for i, player in pairs(plrs) do
		local jewels = game.Players:FindFirstChild(player).jewels.Value
		print(jewels)
		playerRankingsDictionary[player] = jewels
		
	end 
end

local function game_ended()
	SortByGemsDescending(plrs)
	wait(1)
	for k,_ in pairs(playerRankingsDictionary) do
		table.insert(names, k)
	end
	
	table.sort(names)
	for k, v in pairs(playerRankingsDictionary) do
		print(k.."="..v)
	end
	
end

game_ended()

Making a leaderstats automatically sorts the points from top = greater bottom = least

You can’t really sort a dictionary, you’d need to keep it as an array, like so:

local plrs = {}
local playerRankingsDictionary = {}
local names = {}

local function SortByGemsDescending(plrs)
	for i, player in pairs(plrs) do
		local jewels = game.Players:FindFirstChild(player).jewels.Value
		print(jewels)
		playerRankingsDictionary[player] = jewels
		
	end 
end

-- Convert the dict into an array instead, so you can use the table.sort method
local function SortByGemsDescending(plrs)
	local playerRankingsDictionary = { }
	for i, player in next, plrs do
		player = player:IsA 'Player' and player or (type(player) == 'string' and game.Players:FindFirstChild(player) or nil)
		if player and player:FindFirstChild 'jewels' then
			playerRankingsDictionary[#playerRankingsDictionary + 1] = {player = player; jewels = player.jewels.Value}
		end
	end
	table.sort(playerRankingDictionary, function (a, b)
		-- a.jewels > b.jewels is descending order
		-- if you wanted ascending, you would do: a.jewels < b.jewels
		return a.jewels > b.jewels
	end)
	return playerRankingsDictionary
end

-- For you to test:
local function printGemList(plrs)
	--[[
		Should print e.g.:
			Player1 is 1st with 10 jewels
			Player2 is 2nd with 5 jewels
			n-player is n-th with n jewels
	]]
	local function ordinalNumber(n)
		local ordinal, digit = {"st", "nd", "rd"}, string.sub(n, -1)
		if tonumber(digit) > 0 and tonumber(digit) <= 3 and string.sub(n,-2) ~= 11 and string.sub(n,-2) ~= 12 and string.sub(n,-2) ~= 13 then
			return n .. ordinal[tonumber(digit)]
		else
			return n .. "th"
		end
	end

	local orderedList = SortByGemsDescending(plrs)
	for i, packet in next, orderedList do
		if packet.player and packet.player.Parent then
			print(
				('%s is %d%s with %d jewels!'):format(packet.player.Name, i, ordinalNumber(i), packet.jewels)
			)
		end
	end
end

Also, if it’s the ‘print(jewels)’ that’s printing 0 in your original ‘SortByGemsDescending()’ function, are you sure you’re updating these values from a server script and not a local script? If this game_ended() function is running on a server and you’ve been updating the jewels value on the client without informing the server, then it’s because it’s not replicated and you will need to make use of RemoteEvents (or pass the decision making for incrementing jewels to the server entirely)

1 Like

You can use table.sort with a comparator function as a second argument.

table.sort(plrs, function(player1, player2)
	assert(player1:FindFirstChild("jewels") ~= nil, player1.Name.." has no Jewels stat.")
	assert(player2:FindFirstChild("jewels") ~= nil, player2.Name.." has no Jewels stat.")
	return (player1["jewels"].Value < player2["jewels"].Value)
end)

That’s not sorting a dictionary though, you’re sorting the array ‘plrs’? The dictionary in his example is the table ‘playerRankingsDictionary’ with the index as a Player (GameObject) and the value as the jewel (number/int)

12:50:53.974 - Workspace.Arena.Part.OnPlrHit:10: attempt to call a nil value

Change the line to

player = player and (type(player) == 'string' and game.Players:FindFirstChild(player) or (typeof(player) == 'Instance' and player or nil)) or nil
1 Like

Could this still be the problem if there’s only one person in the array and it works fine, but if there’s more than one person it always says 0 jewels?

It would work fine with the code I posted above, but I have no idea how you’re determining who’s name is put inside of the ‘plrs’ array. Could you post that code and I can tell you if that’s wrong?

1 Like
local plrs = {}

local plr_limit = 6

local function check_plr(plr)
	local found_plr = false
	
	for _,v in pairs(plrs) do
		if v == plr then
			found_plr = true
			break
		end
	end
	
	return found_plr
end

	
script.Parent.Touched:Connect(function(p)
  if #plrs < plr_limit and p.Parent:FindFirstChildOfClass("Humanoid") and not check_plr(p.Parent.Name) then
	    table.insert(plrs, p.Parent.Name)
		game.Workspace:WaitForChild(p.Parent.Name).Head.BillboardGui.Enabled = true
	  elseif #plrs >= plr_limit and not func_started then
		func_started = true
    	start_script()
  end
end)

This is the whole script just in case you want to see it

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local plrs = {}
local plr_limit = 2
local func_started = false

local function SortByGemsDescending(plrs)
	local playerRankingsDictionary = {}
	for i, player in next, plrs do
		player = player and (type(player) == 'string' and game.Players:FindFirstChild(player) or (typeof(player) == 'Instance' and player or nil)) or nil
		if player and player:FindFirstChild 'jewels' then
			playerRankingsDictionary[#playerRankingsDictionary + 1] = {player = player; jewels = player.jewels.Value}
		end
		
	end 
	
	table.sort(playerRankingsDictionary, function (a, b)
		
		return a.jewels > b.jewels
	end)
	return playerRankingsDictionary
end

local function printGemList(plrs)
	
	local function ordinalNumber(n)
		local ordinal, digit = {"st", "nd", "rd", "th", "th"}, string.sub(n, -1)
		if tonumber(digit) > 0 and tonumber(digit) <= 3 and string.sub(n, -2) ~= 1 and string.sub(n, -2) ~= 2 and string.sub(n, -2) ~= 3 then
			return n .. ordinal[tonumber(digit)]
		else
			return n .. "th"
		end
	end
	
	local orderedList = SortByGemsDescending(plrs)
	for i, packet in next, orderedList do
		if packet.player and packet.player.Parent then
			print(
				('%s is %d%s with %d jewels!'):format(packet.player.Name, i, ordinalNumber(i), packet.jewels)
			)
		end
	end
end

local function game_ended()
	SortByGemsDescending(plrs)
	printGemList(plrs)

	
end

local function start_script()
	local timeLeft = 20
	for i = 1, timeLeft do
		for i, player in pairs(plrs) do
			local plr = game.Players:FindFirstChild(player)
			timeLeft -= 1
			plr.gameTime.Value -= 1
			if timeLeft == 1 then
				print("fire game ended")
					game_ended()
				end
		end
		wait(1)
	end
end

local function check_plr(plr)
	local found_plr = false
	
	for _,v in pairs(plrs) do
		if v == plr then
			found_plr = true
			break
		end
	end
	
	return found_plr
end

	
script.Parent.Touched:Connect(function(p)
  if #plrs < plr_limit and p.Parent:FindFirstChildOfClass("Humanoid") and not check_plr(p.Parent.Name) then
	    table.insert(plrs, p.Parent.Name)
		game.Workspace:WaitForChild(p.Parent.Name).Head.BillboardGui.Enabled = true
	  elseif #plrs >= plr_limit and not func_started then
		func_started = true
    	start_script()
  end
end)

There’s no reason why this would stop it from counting the jewels. How have you implemented the updating of the gem value? Is it a local script or a server script?

Similarly, is the script you posted above in a local or a server script?

Some changes for some extra checks:

local ReplicatedStorage = game:GetService("ReplicatedStorage")

local DEBUG = true --> Turn it off to stop printing the gem data
local plrs = { }
local plr_limit = 2
local func_started = false

local function SortByGemsDescending(plrs)
	local playerRankingsDictionary = { }
	for i, player in next, plrs do
		player = game.Players:FindFirstChild(player)
		if player and player:IsA 'Player' and player:FindFirstChild 'jewels' then
			playerRankingsDictionary[#playerRankingsDictionary + 1] = {player = player; jewels = player.jewels.Value}
		end
	end 
	
	table.sort(playerRankingsDictionary, function (a, b)
		return a.jewels > b.jewels
	end)
	return playerRankingsDictionary
end

local function printGemList(plrs)
	local function ordinalNumber(n)
		local ordinal, digit = {"st", "nd", "rd", "th", "th"}, string.sub(n, -1)
		if tonumber(digit) > 0 and tonumber(digit) <= 3 and string.sub(n, -2) ~= 1 and string.sub(n, -2) ~= 2 and string.sub(n, -2) ~= 3 then
			return n .. ordinal[tonumber(digit)]
		else
			return n .. "th"
		end
	end
	
	local orderedList = SortByGemsDescending(plrs)
	for i, packet in next, orderedList do
		if packet.player and packet.player.Parent then
			print(
				('%s is %d%s with %d jewels!'):format(packet.player.Name, i, ordinalNumber(i), packet.jewels)
			)
		end
	end
end

local function game_ended()
	--> Do stuff with the ranked list here
	local rankedByJewelList = SortByGemsDescending(plrs)
	
	--> Print the gem list for debug purposes
	if DEBUG then
		printGemList(plrs)
	end
	
	--> Reset our func state
	func_started = false
end

local function start_script()
	--> If we've already started, don't start it again
	if func_started then
		return
	end
	local timeLeft = 20
	for i = 1, timeLeft do
		for i, player in pairs(plrs) do
			local plr = game.Players:FindFirstChild(player)
			timeLeft = timeLeft - 1
			plr.gameTime.Value = plr.gameTime.Value - 1
			if timeLeft == 1 then
				print("fire game ended")
				game_ended()
				break
			end
		end
		wait(1)
	end
end

local function check_plr(plr)
	local found_plr = false
	
	for _,v in pairs(plrs) do
		if v == plr then
			found_plr = true
			break
		end
	end
	
	return found_plr
end

	
script.Parent.Touched:Connect(function (p)
  if #plrs < plr_limit and p and p.Parent and p.Parent:FindFirstChildOfClass("Humanoid") and not check_plr(p.Parent.Name) then
	--> Let's make sure all the pieces are in the right place, if so, we can add them to our list
	if p.Parent:FindFirstChild 'Head' then
		if p.Parent.Head:FindFirstChild 'BillboardGui' then
			table.insert(plrs, p.Parent.Name)
			p.Parent.Head.BillboardGui.Enabled = true
		end
	end
	--> Start it now if we have enough players
	if #plrs >= plr_limit and not func_started then
		func_started = true
		start_script()
	end
  elseif #plrs >= plr_limit and not func_started then
	func_started = true
	start_script()
  end
end)
1 Like

Thank you so much! It’s working fine now, but is there a way to make it so when you sort the player into the playerRankingsDictionary you add their name instead of a letters and numbers like this: 0xb1065764b8a63133?

So the hash you sent above is actually what’s called a table reference, it’s the reference in memory to that table.

In the function:

local function game_ended()
	--> Do stuff with the ranked list here
	local rankedByJewelList = SortByGemsDescending(plrs)
	
	--> Print the gem list for debug purposes
	if DEBUG then
		printGemList(plrs)
	end
	
	--> Reset our func state
	func_started = false
end

When you define the variable ‘rankedByJewelList’ as the return value from SortByGemsDescending() function, it returns a table such that:

-- Let's pretend both you and I are in a game, and you have 5 jewels and I have 2

local rankedByJewelList = SortByGemsAscending(plrs)
-- The table actually looks like this:
--[[
local rankedByJewelList = {
	[1] = {player = game.Players.Play_MazeOfHeck; jewels = 5}; -- You're the 1st index as you have the most jewels
	[2] = {player = game.Players.Isocortex;, jewels = 2};
}
]]

Therefore, if you want the person with the most jewels, you would just index the table with its 1st index i.e.

local MostJewelsPacket = rankedByJewelList[1]
local MostJewelsCount = MostJewelsPacket.jewels
local MostJewelsPlayer = MostJewelsPacket.player

Or, if you wanted to iterate through the result to get each index:

for WhatTheyPlaced, Packet in next, rankedByJewelList do
	local player = Packet.player -- the player we're looking at
	local jewels = Packet.jewels -- the jewels they managed to get
	
	print(player.Name, 'came', WhatTheyPlaced, 'with', jewels, 'jewels!')
	-- In the case above (with you and I in a game), this would print something like:
	--[[
		Play_MazeOfHeck came 1 with 5 jewels!
		
		Isocortex came 2 with 2 jewels!
	]]
end
``
1 Like