# Return the success or failure of an event occurring with a given percentage C#

I'm working on a skill based system that does away with the concept of RNG despite player skill. Basically, my concept is this:

A player's chance to make something is based on their knowledge in the materials they're working with + their experience in making the actual item *(# of times they've successfully made it)* + their experience in the actual activity *(in this case, crafting)*. All values have a numerical value and the percentage of making it is determined later.

So given the following:

PlayerSkillRating = 15 + 0 + 20 = 35;
ChanceToMake = (PlayerSkillRating / Item Skill Required To Make) * 100


Longsword

Quality      Item Skill Required To Make
Low          15 * 2.5 = 37.5
Good         37.5 * 2.5 = 93.75
High         93.75 * 2.5 = 234.4
Great        234.4 * 2.5 = 586
Excellent    586 * 2.5 = 1465
Master       1465 * 5 = 7324.22
Perfect      7324.22 * 5 = 36621.1


Plugging in the formula, we get the chance this particular player can make the item at each quality tier successfully.

Bad = 233%
Low = 93.3%
Good = 37.3%
High = 15%
Great = 6%
Excellent = 2.4%
Master = 0.5%
Perfect = 0.1%


So a player who has never crafted a longsword before, will still have a high chance to make a low quality item and a decent chance to make a good quality item because they know the materials they're working with and they have experience in the skill itself.

If the chance to make a quality item exceeds 100%, that tier basically 'drops off', meaning the player will never make that tier item again*(it basically resets to 0)*.

So I have this pseudo code, I know how I want it to work, but my question is, how do I implement it in code? Do I still generate a random number when the skill is used and compare it to the player's chance to make percentage?

OnSkillUse()
{
var randNum = new Random();

if((ChanceToMake / 100) > randNum)
return "Failure";
return "Success";
}


From what I understand, this is similar to how it's done today and I'm still basing the entire system on RNG to figure out whether it was successful or not.

Is there a better way of accomplishing this since I basically already know the probability of making the item every time the player uses the skill? Or am I simply just repeating a system that's already being used?

• I am confused. You say you don't want a system based on random numbers, and yet you have a design concept which is full of probabilities. So do you want a deterministic crafting system or not? – Philipp Jun 16 '19 at 14:33
• The system you described in no way avoids RNG. It's based on having a chance of success and the default way to implement that is with a RNG. And this is also one of the main ways RNG's are used in games. Most games have some "modifiers" to make things more or less likely, but they still use RNG's. – Bernhard Barker Jun 16 '19 at 15:14
• Hence the question – Robert Jun 16 '19 at 15:47
• If folks are finding your question unclear, saying just "hence the question" doesn't clarify it. What's your ideal outcome here? A function that always returns success when called with one set of numeric inputs / always returns failure with a different set of numeric inputs? Or one that can return success or failure non-deterministically in a particular ratio for the same set of inputs (ie. an RNG)? It sounds as though you want to randomly select a result with a particular probability but just not call that selection "random"? Note that a flip of a weighted coin is still random, just biased. – DMGregory Jun 16 '19 at 17:49
• Possible duplicate of How do I create a weighted collection and then pick a random element from it? – Theraot Jun 17 '19 at 9:33

Well, you need a RNG (Random Number Generator) that works with the probabilities you come up with. Yeah, you are not getting rid of RNG, in fact, what you describe is not unheard of in gaming.

What you want is take a randomic: a random variable with uniform distribution in the range (0, 1]. And then mold the randomic to the probability distribution you want.

You should be able to get a randomic in virtually every platform. You will rarely be programming in a platform that does not have a library with a suitable random. And if there isn't (GLSL comes to mind), then go ahead an implement some kind of congruential random number generator (I suggest the Linear congruential generator, for its ease of implementation, unless you have more specific requirements).

Notes:

• I am assuming we do not need a Cryptographically secure random.
• Yes, this is all "pseudorandom". I just didn't want to write "pseudo" everytime. It is meaningless knowing the context.

What I will describe here is inteded for a discrete list of values with their probabilities.

Usually the first step is to normalize the distribution. The total probability of all the cases must be 100%.

However, you have an special requirement that a porbability beyond 100% will become 0%, so let us start there...

Input distribution:

Low = 93.3%
Good = 37.3%
High = 15%
Great = 6%
Excellent = 2.4%
Master = 0.5%
Perfect = 0.1%

Low = 93.3%
Good = 37.3%
High = 15%
Great = 6%
Excellent = 2.4%
Master = 0.5%
Perfect = 0.1%


To normalize, we will add the probabilities and divide

Adjusted distribution:

Low = 93.3%
Good = 37.3%
High = 15%
Great = 6%
Excellent = 2.4%
Master = 0.5%
Perfect = 0.1%

Total: 154.6%

Normalized distribution:

Bad = 0 / 154.6 = 0%
Low = 93.3 / 154.6 = 60.349288486%
Good = 37.3 / 154.6 = 24.126778784%
High = 15 / 154.6 = 9.702457956%
Great = 6 / 154.6 = 3.880983182%
Excellent = 2.4 / 154.6 = 1.552393273%
Master = 0.5 / 154.6 = 0.323415265%
Perfect = 0.1 / 154.6 = 0.064683053%

Total: 99.999999999% (100% within rounding error)


Notes

• This would work even if the input values were not meant to be probabilities, they are just weights.
• Scaling all input values by a given factor will not affect this result.
• We have to decide where will the rounding error be chocked to. I will suggest to leave the error on the last category (which is what happens naturally with this method).
• If you can work with arbitrary precision or with fractional number, you can do this without rounding error. It is usually not worth it.

The next step is to figure out the accumulated distribution:

Normalized distribution:

Low = 60.349288486%
Good = 24.126778784%
High = 9.702457956%
Great = 3.880983182%
Excellent = 1.552393273%
Master = 0.323415265%
Perfect = 0.064683053%

Accumulated distribution:

Low = 60.349288486% + 0% = 60.349288486%
Good = 24.126778784% + 60.349288486% = 84.47606727%
High = 9.702457956% + 84.47606727% = 94.178525226%
Great = 3.880983182% + 94.178525226% = 98.059508408%
Excellent = 1.552393273% + 98.059508408% = 99.611901681%
Master = 0.323415265% + 99.611901681% = 99.935316946%
Perfect = 0.064683053% + 99.935316946% = 99.999999999%


In case that wasn't clear, we are adding the previous total each step, so that we are accumulating the values. We should get 100% (within rounding error) at the end.

Finally, we take our randomic and choose...

pseudo code:

var randomic = rand();
if (randomic < 0%)
{
}
if (randomic < 60.349288486%)
{
return "Low";
}
if (randomic < 84.47606727%)
{
return "Good";
}
//...
if (randomic < 99.935316946%)
{
return "Master";
}
return "Perfect"; // <- no check, sleight of hand on the rounding error


You can imagine how you could turn your data into a dictionary, and then the code above would become a loop. And of course the randomic is in the range [0, 1), so you need your probabilities in the range [0, 1). I leave that to you.

Addendum 1: I totally ignored the fact that we are talking C#. Random.NextDouble is where you get your randomic. And to store the distribution, an IEnumerable of tuples or KeyValuePair will do.

OnSkillUse()
{
var randNum = new Random();

if((ChanceToMake / 100) > randNum)
return "Failure";
return "Success";
}


By the way, that is not valid C#

Flip the conditional:

OnSkillUse()
{
var randNum = new Random();

if(randNum < (ChanceToMake / 100))
return "Failure";
return "Success";
}


That is the same thing I have above - assuming randNum is the randomic - except you only have two cases: "Success" and "Failure". The accumulated probability of "Failure" is only the probability of "Failure" because there are no more cases before.

By the way, note that if ChanceToMake is 0, then randNum will never be less than ChanceToMake (if ChanceToMake is 0 then ChanceToMake / 100 will never be more than randNum)... which means that it will never enter the if, therfore it will always lead to "Success". I do not think you meant to say that a ChanceToMake of 0 means certain "Success". I'm guessing you have "Success" and "Failure" inverted.

We could talk about entropy. You see, your RNG uses a seed, which should be a source of entropy. When you say new Random() you are leaving that to the system, however you could take control of the seed. Knowing the seed, your random number generator will be completely predictable (hence, "pseudorandom")... so, one option is to seed it with something with less entropy ("less random").