On the Lambda

Programming, Technology, and Systems Administration

On the Lambda

Simulated Dice Rolls with C# and Linq

March 22nd, 2013 · No Comments · c#, development

A common feature of certain table top role playing games is to have players roll dice for their character’s starting ability scores. Typically a score is the result of rolling three six-sided dice and ranges from 3 to 18. Quick: what is the expected outcome of a such a role? The answer here is fairly simple; most people know, or can intuit, that a single roll has an average of 3.5, so three of them would add to 10.5. That is the average stat score.

But let’s make this more complex. It’s also common for players to be allowed to roll four dice, and then throw away the lowest score. What now is the average stat score? If you know your probability and statitics or are will to lookup the formula you can still work this out in your head, but it’s a lot trickier, and not at all intuitive. In my case, somewhere along the way I forgot the math involved.

C# to the rescue! I don’t need to know the probability rules to find this answer. I can write a program to simulate a few million dice rolls and tell me the result. Of course, any language can do that. What makes this fun is writing each simulation run as a one-liner using linq. I’m not a fan of the query comprehension syntax, but I will make generous use of the linq extension methods.

Here’s the code:

var r = new Random();
int iterations = 10000000;
double result;
 
//roll three dice
 result = Enumerable.Range(0, iterations).Select(i => Enumerable.Range(0, 3).Select(o => r.Next(1,7)).Sum()).Sum() / (iterations * 3.0);
Console.WriteLine(result);
 
//roll 4 dice, drop the lowest
result = Enumerable.Range(0, iterations).Select(i => Enumerable.Range(0, 4).Select(n => r.Next(1,7)).OrderBy(n => n).Skip(1).Sum()).Sum() / (iterations * 3.0);
Console.WriteLine(result);

 

The first round verifies the 3.5 average per dice roll, and the second round discovers the new number when you discard the lowest of four rolls: it looks to be about 4. Be careful, though. It’s not exactly 4. If I add more iterations, the 3.5 number gets accurate quickly to more and more decimal places. The other number, though, seems to settle in somewhere around 4.08. This matters, because if you change the code to the total of all three rolls, rather than dividing by 3, it means you end up with 12.24 instead of just 12. Applying this difference to the original problem of ability score rolls, and if you want to know if your character is above or below average for a typical game with six abilities, the number to beat is now 73.5 instead of just 72. Add up a character’s level 1 abilities, and 73 and below is sub-par, while 74 and above is good.

The code itself is not that interesting. It’s worth pointing out here that the upper bound of the Random.Next() function is exclusive, but the lower bound is inclusive… so a dice roll from 1 to 6 means calling the function r.Next(1,7). But the main trick is the use of a projection (.Select()) on the Enumerable.Range() method to repeat the dice rolls. Once you understand how that works, the rest of it should make a lot of sense… if a bit cryptic. Again, this was an excercise to do this as a one-liner. In real production code I would likely use a regular for loop for at least the outer enumeration simply to improve clarity and read-ability. But I think I’d keep the inner Enumerable.Range() projection.

Tags:

No Comments so far ↓

There are no comments yet...Kick things off by filling out the form below.

Leave a Comment