Make a program that simulates rolling two 6-sided dice and compares the results to expected results in C#

(This example is for a book I'm just starting on. I'll post information on it in a few months when it's closer to publication.)

When you enter a number of trials and click Roll, the program uses the following code to simulate dice rolls and display results. The code that simulates the dice rolls 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 scale_y = ymax / (counts.Max() * 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 expected_points = new List();
            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 an array larger 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.

The code then 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 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 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 well. For example, 100 trials gets you a fairly bad 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.

   

 

What did you think of this article?




Trackbacks
  • No trackbacks exist for this post.
Comments
  • No comments exist for this post.
Leave a comment

Submitted comments are subject to moderation before being displayed.

 Name

 Email (will not be published)

 Website

Your comment is 0 characters limited to 3000 characters.