How to allocate star values based on percents

I’m wanting to set a star value based on a percent of something, simply and cleanly.

--[[ Star values (percents - star)
	91-100 = 5
	81-90 - 4.5
	71-80 = 4
	61-70 - 3.5
	51-60 = 3
	41-50 - 2.5
	31-40 = 2
	21-30 - 1.5
	11-20 = 1
	1-10 = 0.5
]]

local Score = 60 / 100
local Stars = Score * 5 -- == 3 (3 stars is correct)

for i = 1, 5 do
    local Star = frame[i]
    if Stars >= i then -- Full star
        Star.Image = FULL_STAR_IMAGE
    else -- Empty star
        Star.Image = EMPTY_STAR_IMAGE
    end
end

However I’m stuck on how to distinguish half star values and times when the number doesn’t perfectly match the star values

local Score = 75 / 100
local Stars = Score * 5 -- == 3.75 (needs to be 4 stars)

local Score = 82 / 100
local Stars = Score * 5 -- == 4.1 (needs to be 4.5 stars)

Using if statements could be more clean.

However I noticed by plotting on a graph that the points match up similar to a heaviside step function and wanted to overcomplicate things.

image

So I used the analytical solution and plotted this somehow (Using function transformation rules):

Desmos:

local function heavisideAnalytical(k, x, step)
	step = step or 1
	return 1*step/(1+math.exp(-2*k*x))
end

local function getGrade(decimalPercent)
	local grade = 0
	for i = 1, 10 do
		grade += heavisideAnalytical(10000, decimalPercent - 0.1*i + 0.1, 0.5)
	end
	
	return grade
end

print(getGrade(75/100)) -- 4
print(getGrade(82/100)) -- 4
1 Like

I seem to be getting like .25 values included in this?
image

Probably because it’s in the inbetween point,
image

Adding this rounding trick but flooring it instead should solve the issue

local function heavisideAnalytical(k, x, step)
	step = step or 1
	return 1*step/(1+math.exp(-2*k*x))
end

local function getGrade(decimalPercent)
	local grade = 0
	for i = 1, 10 do
		grade += heavisideAnalytical(10000, decimalPercent - 0.1*i + 0.1, 0.5)
	end
	grade = math.floor(grade*2)/2

	return grade
end

print(getGrade(75/100)) -- 4
print(getGrade(82/100)) -- 4.5
print(getGrade(40/100)) -- 2
print(getGrade(50/100)) -- 2.5

Although I really like your solution I think it would be simpler to just do this

local function GetStars(Score)
    return math.min(math.floor(Score/10 + 1)*0.5, 5)
end
1 Like

Whoa yeah that is a lot better, had a feeling I was overcomplicating it.

However found out that has some issues in the transition scores, 40 and 41, 50, 51 due to the inclusive and exclusive nature of the floor function. Math.ceil should reverse it:

local function GetStars(Score)
	return math.min(math.ceil((Score)/10 + 1)*0.5-0.5, 5)
end

print(GetStars(75)) -- 4
print(GetStars(82)) -- 4.5
print(GetStars(40)) -- 2
print(GetStars(41)) -- 2.5
print(GetStars(50)) -- 2.5
print(GetStars(51)) -- 3
1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.