Title: Make a dice-rolling simulation in C#
This example performs a simulation of rolling two six-sided dice. It's related to my book Interview Puzzles Dissected, which includes several examples that involve rolling dice.
When you enter a number of trials and click Roll, the program uses the following code to perform a simulation that rolls two six-sided dice. The code that performs the simulation is relatively short. Most of the code displays the results.
// Generate the rolls.
private void btnRoll_Click(object sender, EventArgs e)
{
picGraph.Image = null;
Cursor = Cursors.WaitCursor;
Refresh();
// Make an array to hold value counts.
// The value counts[i] represents rolls of i.
long[] counts = new long[13];
// Roll.
Random rand = new Random();
long numTrials = long.Parse(txtNumTrials.Text);
for (int i = 0; i < numTrials; i++)
{
int result = rand.Next(1, 7) + rand.Next(1, 7);
counts[result]++;
}
// The expected percentages.
float[] expected =
{
0, 0, 1 / 36f, 2 / 36f, 3 / 36f, 4 / 36f, 5 / 36f,
6 / 36f, 5 / 36f, 4 / 36f, 3 / 36f, 2 / 36f, 1 / 36f
};
// Display the results.
Bitmap bm = new Bitmap(
picGraph.ClientSize.Width,
picGraph.ClientSize.Height);
using (Graphics gr = Graphics.FromImage(bm))
{
gr.SmoothingMode = SmoothingMode.AntiAlias;
gr.TextRenderingHint = TextRenderingHint.AntiAliasGridFit;
using (StringFormat sf = new StringFormat())
{
sf.Alignment = StringAlignment.Center;
sf.LineAlignment = StringAlignment.Far;
float ymax = picGraph.ClientSize.Height;
float scale_x = picGraph.ClientSize.Width / 11;
float max_y = Math.Max(
counts.Max(),
numTrials * expected.Max());
float scale_y = ymax / (max_y * 1.05f);
for (int i = 2; i <= 12; i++)
{
gr.FillRectangle(Brushes.LightBlue,
(i - 2) * scale_x, ymax - counts[i] * scale_y,
scale_x, counts[i] * scale_y);
gr.DrawRectangle(Pens.Blue,
(i - 2) * scale_x, ymax - counts[i] * scale_y,
scale_x, counts[i] * scale_y);
float percent = 100 * counts[i] / (float)numTrials;
float expected_percent = 100 * expected[i];
float error = 100 * (expected_percent - percent) /
expected_percent;
string txt = percent.ToString("0.00") +
Environment.NewLine +
expected_percent.ToString("0.00") +
Environment.NewLine +
error.ToString("0.00") + "%";
gr.DrawString(txt, this.Font, Brushes.Black,
(i - 2) * scale_x, ymax - counts[i] * scale_y);
}
// Scale the expected percentages for the number of rolls.
for (int i = 0; i < expected.Length; i++)
{
expected[i] *= numTrials;
}
// Draw the expected results.
List<PointF> expected_points = new List<PointF>();
expected_points.Add(new PointF(0, ymax));
for (int i = 2; i <= 12; i++)
{
float y = ymax - expected[i] * scale_y;
expected_points.Add(new PointF((i - 2) * scale_x, y));
expected_points.Add(new PointF((i - 1) * scale_x, y));
}
expected_points.Add(new PointF(11 * scale_x, ymax));
using (Pen red_pen = new Pen(Color.Red, 3))
{
gr.DrawLines(red_pen, expected_points.ToArray());
}
}
}
picGraph.Image = bm;
Cursor = Cursors.Default;
}
The code makes a counts array to record the number of rolls that give each of the possible totals 2 through 12. It actually allocates a larger array with bounds 0 through 12 so the code can record the number of times the total is K in counts[K].
Next, the code performs its trial rolls. It simply loops over the number of trials and uses a Random object to generate two "rolls" between 1 and 6. Note that you need to do this to get the proper distribution to simulate the dice. You can't just pick a random number between 2 and 12. The code then adds 1 to the count for that roll.
Now the code defines the expected fractions for each total roll. For example, there are 3 ways you can roll a total of 4 (1+3, 2+2, and 3+1) and 36 possible rolls, so the expected probability of rolling 4 is expected[4] = 3/36. Like the counts array, the expected array allocates extra entries so the expected probability for rolling a total of K can be stored in expected[K].
Next the code starts drawing the results. It creates a Bitmap and an associated Graphics object.
It then loops over the roll result values 2 through 12. For each value, the code draws a rectangle showing how many times the roll occurred. It draws text below the top of the rectangle showing the percentage of times that the value was rolled, the expected percentage, and the percent error between the two.
The code then loops through the expected values and draws them in red so you can easily compare the expected and actual results.
If you run the example, you may be surprised at how many trials you need to run to consistently get results that match the expected values closely. For example, 100 trials gets you a fairly poor match, often giving results more than 20% off the expected values. Even with 1,000 trials you'll often get results more than 15% from their expected values. It isn't until you perform on the order of 10,000 trials that you start to get consistently good matches.
Download the example to experiment with it and to see additional details.
|