0
\$\begingroup\$

Say for example in my game a player is able to kill an NPC, and there is an x% chance of an item dropping.

When calculating if the item actually dropped or not, what is the best way to calculate a success or failure?

Does the context of the action matter? Would you calculate drop chance the same way as a different RNG event? (x% to gain a perk, etc).

\$\endgroup\$
1

1 Answer 1

4
\$\begingroup\$

The simplest way to do random chance events is to generate a random decimal number from 0 to 1, multiplying it by one hundred, and then seeing if that number is less than or equal to the percentage drop chance. Here's some psuedocode:

float random = Random.generateDecimal() * 100;
if(random <= dropChance) /*Give the player the item.*/;

The problem with this is that there is a possibility that your RNG might generate a string of random numbers that results in a number of failures or successes that seems to be disproportionate to the given drop chance even though the outcome was a perfectly possible statistical probability. This will result in either a frustrated player or an unbalanced play session.

I've actually been thinking about this myself for a time, and here is a potential solution:

  1. Determine the drop chance.
  2. Calculate the number of trials for which it would be expected that exactly one trial would result in success.
  3. Randomly generate a list of outcomes that guarantees one success.
  4. Cycle through that list each time the action is executed.

Here's some psuedocode for making the array for clarity's sake:

float dropChance = 0.25;
int numberOfTrials = 1 / dropChance;

int successfulTrialNumber = (int) (Random.generateDecimal() * numberOfTrials)
boolean[] outcomes = new boolean[numberOfTrials];
for(int i = 0; i < outcomes.length; i++) {
    outcomes[i] = (i == successfulTrialNumber);
}

This guarantees that the player won't feel "cheated" while still maintaining enough chance variation to make the outcome seem truly randomly determined. However, the downside to this is that you'll have to keep a separate list of outcomes for each action because different actions have different success rates.

We can fix this by using one array for all of our chance variation events so they don't have to all have the same percent chance of occurring. We'd generate a single master array containing a list of numbers from 1 - 100 (or maybe 1 - 1000 if you wanted some decimal drop chances) and then having all outcomes draw on the next step of this array.

Last bit of pseudocode:

int[] outcomes = new int[100] /*You would fill this array with random numbers from one to one hundred with no repeats.*/
int outcomesPointer = 0;
public void generateOutcome(int percentSuccessChance) {
    if(percentSuccessChance <= outcomes[outcomesPointer]) /*Success*/;
    outcomesPointer++;
}

This helps to minimize the feeling of "cheapness" while maintaining pseudo randomness.

Sources: Here's where I learned that true randomness in games can lead to bad outcomes: https://www.youtube.com/watch?v=sLXLlJ7FhJU

The pseudocode solutions were made up by me on the spot.

\$\endgroup\$
5
  • \$\begingroup\$ Thanks for the answer, very insightful. I am assuming you re-create the list each time, so thats the success/failures aren't repeitive? \$\endgroup\$ Commented Dec 22, 2017 at 18:10
  • 1
    \$\begingroup\$ You're welcome! Yes, you are correct; once you exhaust the list, you generate a new one. \$\endgroup\$ Commented Dec 22, 2017 at 18:14
  • 2
    \$\begingroup\$ The method Hylian suggests is usually refered to as "deck of cards". You have a deck of X cards with Y "hits" / proccs and draw a card each time you want to check for a hit. After drawing all X card, shuffle the deck. This smoothens the RNG but makes it somewhat predictable. More information can be read here: questionablyepic.com/deck-of-cards \$\endgroup\$ Commented Dec 22, 2017 at 18:28
  • 1
    \$\begingroup\$ I would like to add that this approach is a really great idea, and professional games often utilize something similar. For instance, any compliant Tetris implementation is required to use what the call the 7-bag algorithm, basically make a randomized list containing all 7 possible tetraminos and then cycle through the list. \$\endgroup\$
    – prushik
    Commented Dec 22, 2017 at 19:47
  • 1
    \$\begingroup\$ I would also suggest that instead of generating a floating point random and multiplying by 100, you can generate an integer and use a bitmask with rejection sampling. You will be more likely to get a good distribution this way (depending on your underlying RNG). e.g. do { int roll = rand()&0x08; } while (roll >= 6); This avoids many problems that can come with floating point numbers. \$\endgroup\$
    – prushik
    Commented Dec 22, 2017 at 20:31

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .