Randomness is a tool game designers/programmers can use to keep games from being samey/deterministic.

Tabletop games use dice, spinners, or shuffled decks of cards to provide an element of chance.

Digital games rely on random number generators (when someone says they were screwed over by RNG, this is exactly what they’re talking about).

Randomness

What is randomness? This is actually a pretty broad and deep topic, but we only care about some subset of things from a programming/game perspective. Suppose we want to generate a bunch of random numbers, from 1 to 100:

  • What is the distribution of random numbers? Should all numbers in our range show up with equal frequency? Think of something like Catan, where dice are used to determine which spaces produce resources on a given turn. Which number shows up most frequently, and why?

  • When we generate these numbers, we want to make sure someone can’t look at previous output to determine the NEXT output. How would this even be possible, and if it IS possible how do we prevent it?

We can use the Random class in C# to generate random numbers. This is Microsoft’s documentation on the Random class:

Random Class (MSDN Website)

Pseudo-random numbers are chosen with equal probability from a finite set of numbers. The chosen numbers are not completely random because a mathematical algorithm is used to select them, but they are sufficiently random for practical purposes. The current implementation of the Random class is based on a modified version of Donald E. Knuth’s subtractive random number generator algorithm. For more information, see D. E. Knuth. The Art of Computer Programming, Volume 2: Seminumerical Algorithms. Addison-Wesley, Reading, MA, third edition, 1997… . You instantiate the random number generator by providing a seed value (a starting value for the pseudo-random number generation algorithm) to a Random class constructor. You can supply the seed value either explicitly or implicitly:

  • The Random(Int32) constructor uses an explicit seed value that you supply.
  • The Random() constructor uses the default seed value. This is the most common way of instantiating the random number generator.

In .NET Framework, the default seed value is time-dependent.

What the heck does any of this mean, why is it important, and how can we use it to get random numbers?

Here’s a janky example of a function that generates a random number:

	int EthanRandom1()
	{
		return 5;
	}

That’s clearly terrible. How about this?

	int EthanRandom2(int seed)
	{
		return seed;
	}

Also still terrible. How about this?

	int EthanRandom3(int seed)
	{
		return (seed * 8) % 11;
	}

OK, maybe a bit better? But it still isn’t super great…

You can think of the Random class in C# as doing something similar to what we’re doing above, except much more complex. Additionally, the numbers that pop out of the Random class should be uniformly distributed.

Random number generators almost always use a “seed”, which is some initial value that ultimately determines the final output. You’ll see this in many different games that procedurally generate their maps:

When generating random numbers for use in games, one of the most-often used seeds is the system clock. The clock is always changing, meaning the RNG can be seeded with a different value for each run of the game.

So now this should make a bit more sense:

Quote

The Random() constructor uses the default seed value. This is the most common way of instantiating the random number generator.

In .NET Framework, the default seed value is time-dependent.

So how do we use the Random class in practice? We can call the Next function on our Random class instance to get a new random number, like so:

Random rand = new Random();
 
int x = rand.Next(0, 10);

The code above produces a random number between 0 and 9, as per the function description. Notice below how the description says “EXCLUSIVE UPPER BOUND”, meaning we’re getting a random number between 0 and 9, NOT including 10.

We can also generate random numbers between 0 and 1, via NextDouble:

Random rand = new Random();
 
double myRandomNumber = rand.NextDouble();

An example of using Random

If we were going to try recreating Pokemon, how might we attempt that in C#? Let’s set our sights small; intead of recreating ALL of Pokemon, how about just a battling/capturing simulator? This is further practice for taking something described in human language and turning it into code.

Below is the capture method formula for gen’s 3 and 4 of Pokemon (Ruby/Saphire and Diamond/Pearl), taken directly from Bulbapedia:

Capture Method (Generations III and IV)

Modified Catch Rate

Modified Catch Rate, , is calculated as follows:

Where:

  • HPmax is the number of hit points the Pokémon has at full health
  • HPcurrent is the number of hit points the Pokémon has at the moment
  • rate is the catch rate of the Pokémon (which may be modified due to use of apricorn balls or actions in the Safari Zone),
  • bonusball is the multiplier for the Poké Ball used, and
  • bonusstatus is the multiplier for any status condition the Pokémon has (2 for sleep and freeze, 1.5 for paralyze, poison, or burn, and 1 otherwise). Due to a bug in Ruby & Sapphire, if the target Pokémon is afflicted with Toxic Poison, no catch bonus is applied. This was fixed in FireRed and LeafGreen.

Shake Probability

The shake probability is calculated as follows:

Shake checks

To perform a shake check, a random number between 0 and 65535 (inclusive) is generated and compared to b. If the number is greater than or equal to b, the check “fails”.

Four shake checks are performed. The Pokémon is caught if all four shake checks succeed. Otherwise, the Poké Ball will shake as many times as there were successful shake checks before the Pokémon breaks free.

If we wanted to generate a random number to be used with this formula, we could do the following:

Random rand = new Random();
 
float formula_B = //Pretend this is all the stuff from our formulas above
 
int shakeChecks = 0;
for(int i = 0; i < 4; ++i)
{
	if(rand.Next(0, 65536) < formula_B)
	{
		++shakeChecks;
	}
}
 
if(shakeChecks == 4)
{
	//Pokemon is caught
}

Notice above we’re calling rand.Next(0, 65536)… remember the exclusive upper bound!

Questions

  • When we type out the capture formula in code, what types should we use? What about the number literals?

  • If we have a function called CatchPokemon, what arguments should it take? What should it return?

  • For the capture formula above, enums might fit in really well for a few things… which fields above could be enums?