Random blocks not being 'random'

Note, this isn’t my code. I’ve just found it from the toolbox and made a few edits to clean it up.

local function RandomChance(number)
	if type(number) == 'number' and number < 100 then
		if math.random(number, 1000) < 500 then
			if math.random(number, 100) == math.random(number, 100) then
				return true
			else
				return false
			end
		else
			if math.random(1, 1337) == 777 then
				return true
			else
				return false
			end
		end
	else
		return false
	end
end

local function GetBlock(position)
	local Add = math.random(1, 10)
	local NonLayerChance = 100
	
	if Add and position then
		if position > LayerLimits.Dirt then
			NonLayerChance = 90
			Add = tonumber(math.abs(math.floor((position - LayerLimits.Surface) / 100)) + math.random(1, 10)) or 1
		elseif position > LayerLimits.Stone then
			NonLayerChance = 80
			Add = tonumber(math.abs(math.floor((position-LayerLimits.Surface) / 100)) + math.random(15, 30)) or 1
		elseif position > LayerLimits.Marble then
			NonLayerChance = 70
			Add = tonumber(math.abs(math.floor((position-LayerLimits.Surface) / 100)) + math.random(25, 50)) or 1
		elseif position > LayerLimits.Granite then
			NonLayerChance = 60
			Add = tonumber(math.abs(math.floor((position-LayerLimits.Surface) / 100)) + math.random(35, 70)) or 1
		elseif position > LayerLimits.Obsidian then
			NonLayerChance = 50
			Add = tonumber(math.abs(math.floor((position-LayerLimits.Surface) / 100)) + math.random(60, 120)) or 1
		else
			NonLayerChance = 40
			Add = tonumber(math.abs(math.floor((position-LayerLimits.Surface) / 100)) + math.random(75, 150)) or 1
		end 
	else 
		NonLayerChance = 100
		Add = 1
	end
	
	if math.random(1, GetChance(11, (23 - Add))) == 1 and (position > LayerLimits.Granite) then
		return Blocks['Copper']
	elseif math.random(1, GetChance(11, 27 - Add)) == 1 and (position > LayerLimits.Obsidian) then
		return Blocks['Silver']
	elseif math.random(1, GetChance(11, 32 - Add)) == 1 and (position < LayerLimits.Dirt) then
		if RandomChance(7) then
			return Blocks['Amethyst']
		else
			return Blocks['Gold']
		end
	elseif math.random(1, GetChance(18, 35 - Add)) == 1 and (position < LayerLimits.Stone) then 
		if RandomChance(8) then
			return Blocks['Amethyst']
		else
			return Blocks['Ruby']
		end	
	elseif math.random(1, GetChance(14, 32 - Add)) == 1 and (position < LayerLimits.Stone) then 
		if RandomChance(9) then
			return Blocks['Amethyst']
		else
			return Blocks['Sapphire']
		end
    end
end

Looking at these lines

if math.random(1, GetChance(11, (23 - Add))) == 1 and (position > LayerLimits.Granite) then
		return Blocks['Copper']
	elseif math.random(1, GetChance(11, 27 - Add)) == 1 and (position > LayerLimits.Obsidian) then
		return Blocks['Silver']
	elseif math.random(1, GetChance(11, 32 - Add)) == 1 and (position < LayerLimits.Dirt) then
		if RandomChance(7) then
			return Blocks['Amethyst']
		else
			return Blocks['Gold']
		end
	elseif math.random(1, GetChance(18, 35 - Add)) == 1 and (position < LayerLimits.Stone) then 
		if RandomChance(8) then
			return Blocks['Amethyst']
		else
			return Blocks['Ruby']
		end
end

As every ore I find is either Copper, Silver, or Ruby. So I’m guessing the math.random isn’t that random, as it’s never getting furhter into the script to give off other ores. Is there a better to do this? I feel it’s too clunk but I don’t know how to successfully have it be random, but still increase odds of finding rare ores the further down a player is

2 Likes

I don’t really know how to make it more “random”, but what I do know is that there is no true randomness from a computational perspective.

Well is there a way for me to set that chances of ores spawning? LIke a percentage, and have the percentages increase/decrease based on the players depth

You can make a table with items based on their chance. Example:

local chanceTable = {"ruby", "ruby", "ruby", "diamond"}

So use math.random to get a random component in the table. Here, the chance of getting “ruby” is 75%, and “diamond” 25%.

What happens if I wanted an ore to be like 1/5000? I’d have to have 4999 other options

Hi. Are you sure you used math.randomseed in this script or another one? This could be making the script return similar results everytime.
Also math.random(1, 5000) == 1 could represent a chance in 5000 of returning the desired value. In fact that’s not true (math.random returns a pseudo-random number), but this doesn’t mean you can’t use this method to set specific chances. Statistically, the real chance will be similar to 1/5000 using different seeds.

Well cause if I did

if math.random(1, 10) == 1 then do
    print('Copper')
elseif math.random(1, 25) == 1 then do
    print('Silver')
elseif math.random(1, 50) == 1 then do
    print('Ruby')
elseif math.random(1, 5000) == 1 then do
    print('Fire Crystal')
end

That’s not technically a 1 in 5000 shot. As it would have to get through the other random above it first, before the 1 in 5000

Well, I’ve always worked with probabilities and math.random using this method (not necessarily the best, but that’s all I needed; maybe a more experienced user can help you):

local RandomNumber = math.random(1, 5000)
if RandomNumber <= 500 then --10%
    print('Copper')
elseif RandomNumber > 500 and RandomNumber <= 700 then --4%
    print('Silver')
elseif RandomNumber > 700 and RandomNumber <= 800 then --2%
    print('Ruby')
elseif RandomNumber == 5000 then --0.02%
    print('Fire Crystal')
end

Note that this wouldn’t work in case the sum of probabilities is bigger than 1.

This is a very inefficient way of programming this which mishajones made clear in his post. Every time you want to add a small item to be generated you have to calculate the math and add another elseif

Instead…

The reason math.random is yielding results that seem non random is because that’s what the method does. Math.Random is a pseudo-random generator. This means there’s an underlying seed that causes the results to created in a “random” pattern.

The weight table is by far the best option in this situation. It’s easy to add and remove options, and it’s fairly easy to understand and manipulate.

https://devforum.roblox.com/t/random-blocks-not-being-random/425000/6?u=mrasync

1 Like

Using the weight table tho, I can’t see a simplified way to edit the values based on a players depth (increasing and decreasing odds of ore spawns)

I wonder if you should try using Random.new() with a seed and then try out nextInteger. That might yield very good results.

Secondly, what does the GetChance function do? I can’t find it in the provided code.

What I would personally do is use a weight table, “convert” it into an array, and use that as a randomizer. I can’t guarantee efficiency though.

local chances = {
   Gold = 20; -- 20/5000
   Silver = 60; -- 60/5000
   Bronze = 4919; -- 4919/5000
   Diamond = 1; -- 1/5000
}
local new_chances = {}

function updateChances()
   new_chances = {}
   for i, v in pairs(chances) do
       for a = 1, v do
          new_chances[#new_chances+1] = i
       end
   end
end
updateChances()

math.randomseed(os.time())

print(new_chances[math.random(1, #new_chances)])

-- lets say i want to set new weight factors
chances.Gold = 99
chances.Silver = 1
chances.Bronze = 0
updateChances()

print(new_chances[math.random(1, #new_chances)])

Simple have a table like this

local thingsToSpawn = {
   Gold = 20;
   Silver = 60;
   Bronze = 4919;
   Diamond = 1;
}

This will allow you to easily add a remove values.

But what if I wanted to have specific depths value etc?

Like a minimum depth for spawning and max depth for spawning? Setting different values for each layer, etc.

local chances = {
   Gold = 20; -- 20/5000
   Silver = 60; -- 60/5000
   Bronze = 4919; -- 4919/5000
   Diamond = 1; -- 1/5000
}

local new_chances = {}

function updateChances()
   new_chances = {}
   for i, v in pairs(chances) do
       for a = 1, #v do -- ERROR HERE
          new_chances[#new_chances+1] = i
       end
   end
end

local function GetBlock(position)
	updateChances()
	math.randomseed(os.time())

	print(new_chances[math.random(1, #new_chances)])
end

attempt to get length of a number value

Subtables would be you answer. You would have to edit your algorithm to pick but you can do something like this,

	local thingsToSpawn = {
		Bronze = {
			maxChance = 100,
			minChance = 80
		},
		Silver = {
			maxChance = 70,
			minChance = 50
		},
		Gold = {
			maxChance = 40,
			minChance = 20
		},
		Diamond = {
			maxChance = 10,
			minChance = 1
		}
	}

For different layers you would have different tables, or if you are OK with having large tables you could have one large table. You could do something like this.

	local thingsToSpawn = {
		ThisLayer = {
			Bronze = {
				maxChance = 100,
				minChance = 80
			},
			Silver = {
				maxChance = 70,
				minChance = 50
			},
			Gold = {
				maxChance = 40,
				minChance = 20
			},
			Diamond = {
				maxChance = 10,
				minChance = 1
			}
		},
		ThatLayer = {
			Bronze = {
				maxChance = 100,
				minChance = 80
			},
			Silver = {
				maxChance = 70,
				minChance = 50
			},
			Gold = {
				maxChance = 40,
				minChance = 20
			},
			Diamond = {
				maxChance = 10,
				minChance = 1
			}
		}
	}

I have mine create an array with all the choices. Then If I have just chosen and entry, I remove it from the array.

You can have it add more entries of a certain type the less it’s chosen, giving it more of a chance to be picked.

You also should change your random seed based on the os.time() if the choices are the same every game.

But really it’s a hat of names. If a name is picked, it’s removed from the hat. If one isn’t picked enough, I add more bits of paper with that name on it.

If I want to be strict, I can just tell it how many times to add each name. So maybe out of 100 or so entries, 20 are Gold, 30 are Silver, and 50 are copper. etc. But you can reduce the work by reducing the numbers and keeping the same chance. Eg, 2, 3, and 5 is the same chance as 20, 30, 50.

If it is crazy amounts 5000+ entries, you could just use a check.

If a drop, then is it a good drop? Value 1 in 5.

If it’s a good drop, is it a really good drop? 1 in 5.

If it’s a really good drop, is it a super good drop? 1 in 5.

If it’s a super good drop, is it an epic drop? 1 in 5.

Same principle though. 4 of the 5 entries are “NormalDrop” the 5th is, “GoodDrop”

If it equals “GoodDrop”, etc.

Or if you are just putting out blocks everywhere, just dump 4000 of bronze at random positions, then dump 300 copper, then 100 silver, then 20 gold.

My blocks are procedurally generated

Not sure where the idea of inefficiency of having many items in a table came from but I’ve heard it before. It’s no more efficient that any other way of doing storing many objects.

There’s no need to have 10 duplicate values? Why would you ever need to do that? I was just posing an example without an regards to actual efficiency between the lines. The only other easy way to do this efficiently is…

local thingsToSpawn = {
        Bronze = {
            ThisLayer = {
                minChance = 10,
                maxChance = 20
            }, 
            ThatLayer = {
                minChance = 50,
                maxChance = 150
            }
        },
        Gold = {
            ThisLayer = {
                minChance = 10,
                maxChance = 20
            }, 
            ThatLayer = {
                minChance = 50,
                maxChance = 150
            }          
        }
    }

I’m unsure of any other alternatives to this.