Voting system code review

Alright guys so I made a voting system, yesterday there was a bug which I asked for help about but I had to fix it myself eventually. Anywho this is the code do you guys think it’s fine?

-- Set the attribute when the player joins. (Server script)
game.Players.PlayerAdded:Connect(function(player)
	player:SetAttribute("Game1Votes", 0)
	player:SetAttribute("Game2Votes", 0)
end)
-- Client voting
local Player = game.Players.LocalPlayer
local PlayerGui = Player.PlayerGui
local RE = game.ReplicatedStorage:WaitForChild("CalculateVotes")
local Game1 = PlayerGui:WaitForChild("ScreenGui"):WaitForChild("Game1")
local Game2 = PlayerGui:WaitForChild("ScreenGui"):WaitForChild("Game2")

Game1.MouseButton1Click:Connect(function()
	local Game1Votes = Player:GetAttribute("Game1Votes")
	
	if Game1Votes and Game1Votes == 0 then
		RE:FireServer("Game1Votes", 1, Game1)
	end
	
	local Game2Votes = Player:GetAttribute("Game2Votes")
	
	if Game2Votes and Game2Votes == 1 then
		RE:FireServer("Game2Votes", 0, Game2)
	end
end)

Game2.MouseButton1Click:Connect(function()
	local Game2Votes = Player:GetAttribute("Game2Votes")

	if Game2Votes and Game2Votes == 0 then
		RE:FireServer("Game2Votes", 1, Game2)
	end
	
	local Game1Votes = Player:GetAttribute("Game1Votes")

	if Game1Votes and Game1Votes == 1 then
		RE:FireServer("Game1Votes", 0, Game1)
	end
end)
-- Add the client's votes to the server's total votes
local RepClients = game.ReplicatedStorage.ReplicateClients
local Calculations = game.ReplicatedStorage.CalculateVotes
local TotalGame1Votes = 0
local TotalGame2Votes = 0

Calculations.OnServerEvent:Connect(function(Player, ReceivedAttribute, Votes, ChosenGame)
	if Player then
		Player:SetAttribute(ReceivedAttribute, Votes)
		print(ReceivedAttribute ..":" .. Player:GetAttribute(ReceivedAttribute))
		if ReceivedAttribute == "Game1Votes" then
			local Game1Votes = Player:GetAttribute(ReceivedAttribute)
			if Game1Votes == 0 then
				TotalGame1Votes -= 1
				RepClients:FireAllClients(ChosenGame.Name, TotalGame1Votes)
			end
			if Game1Votes == 1 then -- This ensures hackers don't give themselves extra votes
				TotalGame1Votes += Game1Votes
				RepClients:FireAllClients(ChosenGame.Name, TotalGame1Votes)
			end
		end
		if ReceivedAttribute == "Game2Votes" then
			local Game2Votes = Player:GetAttribute(ReceivedAttribute)
			if Game2Votes == 0 then
				TotalGame2Votes -= 1
				RepClients:FireAllClients(ChosenGame.Name, TotalGame2Votes)
			end
			if Game2Votes == 1 then -- This ensures hackers don't give themselves extra votes
				TotalGame2Votes += Game2Votes
				RepClients:FireAllClients(ChosenGame.Name, TotalGame2Votes)
			end
		end
	end
end)
-- Replicating the changes to all clients now
local RepClients = game.ReplicatedStorage:WaitForChild("ReplicateClients")
local Player = game.Players.LocalPlayer

RepClients.OnClientEvent:Connect(function(ChosenGame, TotalVotes)
	Player.PlayerGui:WaitForChild("ScreenGui"):WaitForChild(ChosenGame).Text = TotalVotes
end)

I assume there might be a better way to make to make a voting system however I don’t really use tutorials or anything and instead I try to do things myself. I’m happy it works though, I assume it couldn’t be exploited because I added a sanity check to ensure the player’s votes are either 0 or 1 and nothing above that. And it’s more optimized with attributes I think. But maybe I’m wrong. Let me know.

3 Likes

Well guys? Is it good not good?

2 Likes

:grimacing:

Not too good. It could be better.

You could use for loops and table to greatly shorten this code.

2 Likes

Oh ok I’ll delete all the scripts then and make a different system and give up thanks.

2 Likes

Uh huh, good luck with that. I am not gonna stop you or anything.

2 Likes

Bruh what. What’s wrong with you.

2 Likes

You’re suppose to say “NOOOO DON’T DO THAT” and then I’m suppose to say “well if you insist” JUST SAY IT BRO.

2 Likes

That’s what happens when you are feelingless.

Also it’s your choice. I just gave an opinion on your code and your considering it like I am a very good and popular developer who called your code PATHETIC and your entire career depends on that one review. Stop being an attention seeker. Please.

2 Likes

Okay i’m just gonna pretend that you said “noo don’t do that”. WELL IF YOU INSIST.

2 Likes

Also do you not want more friends. I can be one.

2 Likes

Okay, sure.

No thank you.

Please type all of this in one post.

2 Likes

I’ll be your friend don’t worry.

2 Likes

Also hi local human cat that is. yes yes

2 Likes

also i subscribed to your youtube channel https://www.youtube.com/@idk_but-random/videos

2 Likes

The system would be more extendable if you replaced the two attributes with just one representing the players vote. Then once someone votes tally it all up and send it to the clients as a table.

2 Likes

I assume there’s a better way to do what I did. I guess it’s my fault for not using any tutorials and just trying myself.

2 Likes

I would just save 1 table on the server.

Heres a template example of that, using functional programming:

local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local VoteRemote = ReplicatedStorage.Vote

local IsVotingActive = false
local VotingCache = {}

local function ProcessPlayerVote(player: Player, vote: string)
	local userId = player.UserId
	VotingCache[userId] = vote
end

local function ClearVotes()
	table.clear(VotingCache)
end

-- This function will process all votes and return the most voted one
local function GetMostVoted()
	local mostVotedString = ""
	local mostVotedInt = 0
	local votingCount = {}
	
	for _, vote in VotingCache do
		if not votingCount[vote] then
			votingCount[vote] = 0
		end
		
		votingCount[vote] += 1
	end
	
	for voteString, voteCount in votingCount do
		if voteCount > mostVotedInt and mostVotedString ~= voteString then
			mostVotedInt = voteCount
			mostVotedString = voteString
		end
	end
	
	return mostVotedString
end

local function OpenVoting(lifeTime: number)
	if IsVotingActive then
		warn(`Voting is already active!`)
		return
	end
	IsVotingActive = true
	
	local voteRemoteConnection = nil
	voteRemoteConnection = VoteRemote.OnServerEvent:Connect(ProcessPlayerVote)
	
	task.wait(lifeTime)
	
	-- Disable new votes
	voteRemoteConnection:Disconnect()
	
	local mostVoted = GetMostVoted()
	ClearVotes()
	
	-- do soemthing with that
	IsVotingActive = false
end

-- Garbage collecting basically
Players.PlayerRemoving:Connect(function(player)
	local userid = player.UserId
	if VotingCache[userid] then
		VotingCache[userid] = nil
	end
end)

-- Example use
OpenVoting(5) -- opens voting for 5 seconds
2 Likes

As for the client, it should just be able to assume and track its own vote. But if you prefer you could fire a remote to the client confirming the vote went through

2 Likes

If you wanted to add further security, i.e. make sure only lets say 2 things can be voted on, do something like this:

local ValidVotes = {
	"Something1",
	"Something2"
}

local function ProcessPlayerVote(player: Player, vote: string)
	if not table.find(ValidVotes, vote) then
		warn(`{player.Name} attempted to vote for something invalid!`)
		return
	end
	
	local userId = player.UserId
	VotingCache[userId] = vote
end

Full script:

local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local VoteRemote = ReplicatedStorage.Vote

local ValidVotes = {
	"Something1",
	"Something2"
}

local IsVotingActive = false
local VotingCache = {}

local function ProcessPlayerVote(player: Player, vote: string)
	if not table.find(ValidVotes, vote) then
		warn(`{player.Name} attempted to vote for something invalid!`)
		return
	end
	
	local userId = player.UserId
	VotingCache[userId] = vote
end

local function ClearVotes()
	table.clear(VotingCache)
end

-- This function will process all votes and return the most voted one
local function GetMostVoted()
	local mostVotedString = ""
	local mostVotedInt = 0
	local votingCount = {}
	
	for _, vote in VotingCache do
		if not votingCount[vote] then
			votingCount[vote] = 0
		end
		
		votingCount[vote] += 1
	end
	
	for voteString, voteCount in votingCount do
		if voteCount > mostVotedInt and mostVotedString ~= voteString then
			mostVotedInt = voteCount
			mostVotedString = voteString
		end
	end
	
	return mostVotedString
end

local function OpenVoting(lifeTime: number)
	if IsVotingActive then
		warn(`Voting is already active!`)
		return
	end
	IsVotingActive = true
	
	local voteRemoteConnection = nil
	voteRemoteConnection = VoteRemote.OnServerEvent:Connect(ProcessPlayerVote)
	
	task.wait(lifeTime)
	
	-- Disable new votes
	voteRemoteConnection:Disconnect()
	
	local mostVoted = GetMostVoted()
	ClearVotes()
	
	-- do soemthing with that
	IsVotingActive = false
end

-- Garbage collecting basically
Players.PlayerRemoving:Connect(function(player)
	local userid = player.UserId
	if VotingCache[userid] then
		VotingCache[userid] = nil
	end
end)

-- Example use
OpenVoting(5) -- opens voting for 5 seconds
2 Likes