Games often want to use randomness to spice up the experience – it can be a lot more exciting to risk something when you are not entirely certain of the result. In my game abstractanks, the power-ups are generated randomly. However, when playing the game, it seemed like it was always generating the same power-ups in a row, which can be kind of frustrating. Tough luck, because this is just how uniform randomness behaves – as anyone who ever played the board game Sorry! or the german class Mensch ärgere dich nicht! can sure testify.

Let’s try that with C# code:

var outcomes = new []{'A', 'B', 'C', 'D', 'E', 'F'}; for (int i = 0; i < 200; ++i) { var index = random.Next(outcomes.Length-1); Console.Write(outcomes[index]); }

This will produce something like this:

ECDDBABCEEBBDDADEDECCAECECEADBBCCCDAEBEECDBCACAEAA BDEACECBDDBAEDCEEAEAECDEEEECBCCEECEDCBAECCCBDCDDEA CEAABDEEBDEAEBABABDEBDAACBECBBAACAEDEEBAECECECCBAB BBAAEEDEDEEBCACDDEBBCBACADDDBAECBAEDACBEAEABBCAEEA

See all those strings of Cs and Es? Horrible! That does not feel random!

Games Dota 2 work with this by tweaking distribution after each “roll”. Specifically, for things like Phantom Assassin’s Coup de Grace, the chance is increased slightly after each unsuccessful attempt, making the 4th or so attempt a guaranteed critical strike.

But this technique only work nicely for one event in a stream of attempts. It fails to make multiple outcomes look better.

For game I devised an algorithm with two properties:

1. The same roll never appears twice in succession

2. No long stretches without a specific roll

Here’s my algorithm to do that:

var outcomes = new []{'A', 'B', 'C', 'D', 'E', 'F'}; var chances = new []{1.0, 1.0, 1.0, 1.0, 1.0, 1.0}; for (int i = 0; i < 200; ++i) { // Normalize chances so they sum up to 1.0, then build their prefix sum var total = chances.Sum(); var prefixSum = chances .Select(x => x / total) .Aggregate(new List<double>(), (list, x) => { list.Add(list.LastOrDefault() + x); return list; }).ToList(); // Roll an outcome var roll = random.NextDouble(); var pick = prefixSum.FindIndex(x => x >= roll); Console.Write(outcomes[pick]); // Now adapt the distribution by removing all chance from // the last pick and distributing it to the N-1 others var increment = chances[pick] / (chances.Length - 1); chances = chances.Select((x, index) => { if (index == pick) return 0.0; else return x + increment; }).ToArray(); }

And here’s what that produces:

AEDFBCAEFDBAEDFCBDECBFCFDBEACFDECBDAEFCEBACDEAFBDC AFEBCABFDECDAFBECFADFCEBACFDCEDBFAEDCDBDAFEDBFEABD CFABEDFBACDEAEFBECADBAFECAFBDACECFAEBDCAFCDBAEDAFA BDFCDEACDBFEDFCBACDAFBEADFCDEFBEACBEFDCECFABDFCDBE

Much nicer for my needs, but it still looks pretty random. Also, my statistics buddy assured me that this algorithm still guarantees equal outcome probability for all items when run forever. I do now know if this is a new technique, but I did not find anything like this when I was looking for it.

Let me know if this is of any use to you.