I wanted to create a simple simulation for the Monty Hall problem as a fun, quick side project. My plan was to create the full game, forms with clickable doors and all, but I first wanted to create a way to simulate as many games as I wanted to prove to myself that switching doors gives a 66% chance of winning. The problem is that I am getting about 50/50 odds every time.
Can anyone find the problem with my simulation? When I increase the number of doors, the odds of winning after switching doors goes up. However, with 3 doors, I am always getting about 50/50 so I know there is an issue with my code.
private void simulateButton_Click(object sender, EventArgs e)
{
int wins = 0;
int losses = 0;
int attempts = 0;
int numberOfDoors = 3;
// Main game loop. Number of simulations can easily be changed from 1000
for (int games = 0; games < 1000; games++)
{
List<Door> doors = new List<Door>();
for (int i = 0; i < numberOfDoors; i++)
{
doors.Add(new Door
{
Contents = "goat",
Opened = false,
PlayerChoice = false,
});
}
// Randomly pick a door for the car to be in
Random random = new Random();
int doorNumWithCar = random.Next(0, numberOfDoors - 1);
doors[doorNumWithCar].Contents = "car";
// I set player choice to always be door #1 to make it simpler
doors[0].PlayerChoice = true;
// Monty randomly removes one of the 2 doors not chosen by the player.
// (obviously, he doesn't remove the door with the car)
int doorMontyOpens;
while (doors.Count != 2)
{
doorMontyOpens = -1;
while (doorMontyOpens == -1)
{
int possibleDoorToBeShown = random.Next(1, doors.Count);
if (doors[possibleDoorToBeShown].Contents != "car")
{
doorMontyOpens = possibleDoorToBeShown;
}
}
// I just remove the door since that is essentially what Monty Hall is doing when
// He reveals one of the goats
doors.RemoveAt(doorMontyOpens);
}
// Since player choice is always door #1 (index 0) and there are only two doors left,
// we only need to check door #2 at index 1. If it has the car, it is a win. If not, a loss.
// This simulation only tests the case in which the player changes doors after Monty reveals
// a door. So it SHOULD usually have about 667 wins and 333 losses. Why do I get 50/50???
if (doors[1].Contents == "car")
{
wins++;
}
else
{
losses++;
}
attempts++;
this.winsTextbox.Text = $"{wins}";
this.lossesTextbox.Text = $"{losses}";
this.attemptsTextbox.Text = $"{attempts}";
}
MessageBox.Show("Done!");
}
public class Door
{
public string Contents { get; set; }
public bool Opened { get; set; }
public bool PlayerChoice { get; set; }
}
Don't mind the crappiness of the code. This was meant to be a super quick and dirty simulation. Explanations for each section of code are in the comments. Thanks!
The problem is here:
int doorNumWithCar = random.Next(0, numberOfDoors - 1);
The upper bound (the second parameter) of the Next
is exclusive (see the Random.Next(Int32, Int32)
docs) so by using numberOfDoors - 1
you are limiting car to being present only in one of the first two doors, so the original guess is 50/50 instead of ~33/~66 which makes the final guess also 50/50.
It should be
int doorNumWithCar = random.Next(0, numberOfDoors);