Solving With JavaScript: The Game Show Problem

Zak Frisch
WDstack
Published in
14 min readDec 2, 2016

Enter Scene

You are on stage at an amazingly popular, day-time game show. You never thought you would be here but you are, and how fantastic it is!

You are so excited(and so is the crowd, apparently!) that you barely register the words blaring from the mic of the well-dressed host as he announces the prize of today’s game.

“Today you’ll be playing for… a new car! And how do you get that new car? It’s easy! SO easy! My God, we’re practically giving it away!”

As he says this the curtain rises up on stage to reveal …

Three doors! That’s all there is to this game. You pick one! Just one! and behind that door your car awaits!” The host gleams, “Well, if you’re lucky! Otherwise you could win one of these guys!”

The host gestures stage-left and, oddly, a full-grown goat trots out on stage and takes a bow. “City permit not included!”

“Now, Contestant, what door do you choose?”

You hesitate, but mumble out “O — One. One. I want door One!”

The crowd goes wild as the host goes towards the door — you feel like you’re going to faint. “Maybe I should have picked door Two”, you think to yourself.

Then, just as the host is about to reach the handle, he stops. “I’ll tell you what I’ll do,” He walks over to Door 2 and casually opens it, revealing a goat!Look at that Ladies and Gentleman! Whew, it’s a good thing our contestant didn’t pick THIS door, isn’t it?”

The crowd goes wild again, but all you can feel is a creeping numbness slowly coming over your body. The host turns to you with a winning smile on his face.

So. How’s this, Contestant. Door 2 is obviously not the car.” — the crowd erupts with laughter. Once it settles down the Host continues,

“You wanted Door 1. Do you want to stay with Door 1… or, do you want to switch?

The Monty Hall Problem

The above scene is the setup for the Monty Hall Problem. What is the Monty Hall Problem?

The Monty Hall problem is a brain teaser, in the form of a probability puzzle (Gruber, Krauss and others), loosely based on the American television game show Let’s Make a Deal and named after its original host, Monty Hall. — Google

To recap, the setup is this:

  • A Contestant on a Game Show is given the choice between one of three doors.
  • Behind one door there is a brand-new Car. Behind the other two there are Goats.
  • After the Contestant has selected a door, the Game Show Host opens one of the two doors not selected by the Contestant. It reveals a Goat.
  • The Game Show Host asks the Contestant “Do you want to keep your chosen door, or switch to the other door?”

The Conundrum: Is it beneficial to switch, or stay the course?

The Possible Solutions: This question was hotly debated for over two decades. It, quite literally, has had accomplished PhDs’ in statistical mathematics and every other field arguing over the correct answer. As a matter of fact the magazine in which it was originally published received thousands of e-mails and letters regarding the solution when it was first printed. There are only two possible answers, switch or stay. Here are the two most popular explanations for both.

Answer A:

  • It makes no difference.
  • By removing one door the Host has effectively increased your chances to 50/50.
  • Since you have that door and it doesn’t matter, you might as well stay.

Answer B:

  • Based on probability you absolutely must switch.
  • Initially you have a 33% chance(1/3)
  • By removing one door the Host has effectively raised the odds of the other door that you haven’t selected by 1/3. This means by switching you gain a 2/3 advantage over the 1/3 you currently possess.

Which is Correct?
Let’s use JavaScript to test the problem and figure it out!

The Blueprint

The first thing we need to do when designing our test is to figure out what exactly it is that we are going to be building. You may have heard the term pseudo-code — the process of writing out a program in plain text. We’re not going to go deep into pseudo-code, but we want to be certain to write out what we want the code to do so that when we begin coding we don’t have to worry very much, or at all, about missing a piece of the architecture.

What is the setup here?

  • We need 3 doors.
  • As the Game Show: randomly choose a door and declare it as a winner.
  • As the Contestant: randomly pick a door.
  • As the Host: open a door that is not picked by the Contestant, but is not a winner.
  • As the Contestant: either Switch or Stay
  • As the Contestant: Win or Lose.

You’ll notice that this code broken down into parts really isn’t so bad. We’ll go through this list step by step and flesh out the details in JavaScript, then run it 100 times using stay and 100 times using switch. This will give us the probability needed to know whether or not Answer A, or Answer B, is correct.

Step 1: Create 3 Doors.

First things first, we need 3 Doors. When deciding what type of data to use when declaring an architectural piece, whether it be for an application or just a one-off function, it is best if we consider everything we’re going to need out of that item before declaring it.

What do we need out of a door?

  1. We must be able to randomly select a door.
  2. We must be able to assign a door as a winning door or a losing door.
  3. We must be able to select a random losing door.
  4. We must be able to compare doors easily.

With all of that in mind we can choose the best data type for our needs. Depending on your experience you may have already figured this out, but let’s talk it through.

Our ability to randomly select or compare Doors means that it’s very practical to use an Array. This allows us to select a Door by an index number and to iterate through the Array of Doors very easily. The other key piece is our ability to assign whether a Door is the winning door or not. Because we would be assigning a value to the Door it would make sense to use an Object.

Using this thought process we end up with an Array of Door Objects:

var doors = [{
name: 'Door 1',
winner: false,
picked: false,
open: false
}, {
name: 'Door 2',
winner: false,
picked: false,
open: false
}, {
name: 'Door 3',
winner: false,
picked: false,
open: false
}];

We initially give each Door object a property of winner, open and picked with values of false. This allows us to do the least amount of work. How? It’s easier to flip a single value to true than to add or change three values programatically.

Step 2: Randomly choose a door and declare it as a winner.

This step requires quite a deal less thought than the previous, but it still pays to take in the bigger picture. Initially you might think that something like this would be a good fit:

var doors = [{
name: 'Door 1',
winner: false,
picked: false,
open: false
}, {
name: 'Door 2',
winner: false,
picked: false,
open: false
}, {
name: 'Door 3',
winner: false,
picked: false,
open: false
}];
var randomDoor = Math.floor( Math.random() * (3-0)+0 );
doors[randomDoor].winner = true;

You wouldn’t be wrong for doing it this way. If you’re looking only at this particular step it makes perfect sense. When you consider, however, that many steps deal with creating a random number you may try to find a better way.

With the information that in a later step we may be calling for a random number again, and possibly with a different maximum amount(when we look for a random losing, non picked Door), it makes sense if we turn the variable randomDoor into a function that returns a number with x as the max amount:

var doors = [{
name: 'Door 1',
winner: false,
picked: false,
open: false
}, {
name: 'Door 2',
winner: false,
picked: false,
open: false
}, {
name: 'Door 3',
winner: false,
picked: false,
open: false
}];
var randomDoor = (x) => Math.floor( Math.random() * (x-0)+0 );
doors[randomDoor(3)].winner = true;

The above code gives us our winner while simultaneously reducing work we need to do in subsequent steps.

Step 3: Randomly pick a Door

The previous step has made this a lot easier. Since we don’t care what Door is picked(It does not matter whether a picked Door and a winner Door are the same or not) our code is very similar to randomly setting the winner property on a Door. The only difference is that we set the picked property:

var doors = [{
name: 'Door 1',
winner: false,
picked: false,
open: false
}, {
name: 'Door 2',
winner: false,
picked: false,
open: false
}, {
name: 'Door 3',
winner: false,
picked: false,
open: false
}];
var randomDoor = (x) => Math.floor( Math.random() * (x-0)+0 );
doors[randomDoor(3)].winner = true;
doors[randomDoor(3)].picked = true;

Step 4: Open a door that is not picked by the Contestant, but is not a winner.

In this step we are going to want to filter our Doors and randomly select a losing, non picked Door. You might be wondering why we need a random choice of the losing, non picked Doors. We could technically just iterate through the Door Objects and open the first losing, non picked Door we find.

In this particular instance, however, we are trying to model our test case off the information given above. In the circumstances listed we have to assume the Host randomly chooses any losing, non picked Door because there isn’t any data to prove otherwise. We can’t trend data in our model that isn’t existent in the scenario given because the data may inadvertently affect our model and ruin our test. We don’t want that!

We will use our Array’s .filter() method to find Doors which match our criteria winner = false, and return them. After the Door(s) are found an index will be randomly selected and that Door will be checked to see if it was picked. If not it will be opened.

The reason why these two checks are separate is because it allows us to easily flip from one to the other using anti-boolean conversion — or changing 0 to 1(false to true)/changing 1 to 0(true to false), and Type Conversion — changing one type to another. Type Conversion typically involves using the window methods parseFloat(), parseInt(), etc.

A shortcut we can utilize is using the + operator in front of a different data type to change that data type into a number. Sound confusing? Let’s take a look at an example:

console.log( 0 ); // 0
console.log( !0 ); // true
console.log( +!0 ); // 1
console.log( 1 ); // 1
console.log( !1 ); // false
console.log( +!1 ); // 0

What that means is in this very specific instance where we know the currently selected index is either 0 or 1 and there’s only two items in the array, we can check if the currently selected index meets our condition(if picked is true) and if, from that condition, we decide that we want the other item in the array, we can use the +! operators to flip to the other index easily.

When we add this to our code it looks like this:

var doors = [{
name: 'Door 1',
winner: false,
picked: false,
open: false
}, {
name: 'Door 2',
winner: false,
picked: false,
open: false
}, {
name: 'Door 3',
winner: false,
picked: false,
open: false
}];
var randomDoor = (x) => Math.floor( Math.random() * (x-0)+0 );
doors[randomDoor(3)].winner = true;
doors[randomDoor(3)].picked = true;
var losingDoors = doors.filter(obj => (obj.winner === false));
var openNumber = randomDoor(losingDoors.length);
var openDoor = losingDoors[openNumber];
if (openDoor.picked == true) openDoor = losingDoors[+!openNumber];
openDoor.open = true;

Step 5: Either Switch or Stay.

For this step we’re going to turn our code into a function. This will allow us to easily dictate whether the particular running test should switch or stay. We will pass either true(switch) or false(stay) into this function to determine our action.

function runTest(switchOrStay) {
var doors = [{
name: 'Door 1',
winner: false,
picked: false,
open: false
}, {
name: 'Door 2',
winner: false,
picked: false,
open: false
}, {
name: 'Door 3',
winner: false,
picked: false,
open: false
}];
var randomDoor = (x) => Math.floor(Math.random() * (x - 0) + 0);
doors[randomDoor(3)].winner = true;
doors[randomDoor(3)].picked = true;
var losingDoors = doors.filter(obj => (obj.winner === false));
var openNumber = randomDoor(losingDoors.length);
var openDoor = losingDoors[openNumber];
if (openDoor.picked == true) openDoor = losingDoors[+!openNumber];
openDoor.open = true;
}

After we’ve turned it into a function we can then use the value switchOrStay to determine our win condition.

If true(we switch) we will search our doors array for a door that is not picked, not open, and is the winner. If it exists we’ve won. If not we’ve lost.

If false(we stay)we will search our doors array for a door that is picked and is the winner. If it exists we’ve won. If not we’ve lost.

function runTest(switchOrStay) {
var doors = [{
name: 'Door 1',
winner: false,
picked: false,
open: false
}, {
name: 'Door 2',
winner: false,
picked: false,
open: false
}, {
name: 'Door 3',
winner: false,
picked: false,
open: false
}];
var randomDoor = (x) => Math.floor(Math.random() * (x - 0) + 0);
doors[randomDoor(3)].winner = true;
doors[randomDoor(3)].picked = true;
var losingDoors = doors.filter(obj => (obj.winner === false));
var openNumber = randomDoor(losingDoors.length);
var openDoor = losingDoors[openNumber];
if (openDoor.picked == true) openDoor = losingDoors[+!openNumber];
openDoor.open = true;
if (switchOrStay) {
//switching if true
var switchWin = doors.filter(obj => (obj.picked === false && obj.open === false && obj.winner === true));(switchWin.length > 0) ? true : false;}
else if (!switchOrStay) {
//staying
var stayWin = doors.filter(obj => (obj.picked === true && obj.open === false && obj.winner === true));(stayWin.length > 0) ? true : false;}}

In the above code we have determined a win scenario for both switch and stay. We have then computed the condition into a ternary statement. true is win, false is lose. We will change these in the next step so that we can calculate our win/losses.

Step 7: Win or Lose

Almost done! What we need to keep in mind when creating the win condition is that each type of game(switch or stay) needs to collect its own win results.

In order to store our Wins, every time we win we will add a win to the specific integer variable(switchWon or stayWon). When all runs are completed we will output the two numbers. By comparing these numbers we can see if Answer A(50/50), or Answer B(stay is 1/3, switch is 2/3) is correct.

What we’ve been building so far is our function for an individual test. This is what will be run a number of times(100) to determine the correct Answer.

We want the switchWon and stayWon variables to persist beyond each individual run. We can’t declare them within the runTest function because every time we run the test they will be reset to their previous value. We also want to avoid polluting the Global Namespace with more variables.

What we can do in this instance is wrap our functional test(runTest) with another function(MHinit):

function MHinit() {  var switchWon = 0;
var stayWon = 0;
function runTest(switchOrStay) {
...
}
}

The above allows us to store our variables outside of the individual tests and the Global Namespace. This is great because as developers we want as few variables poking into the Global Scope as possible.

An easy way to think about this:

//with wrapped function
Global Scope = [MHinit]
MHinit Scope = [switchWon, stayWon, runTest]
// is better than://without wrapped function
Global Scope = [switchWon, stayWon, runTest]

Moving on — When we win we’re going to need to increment these values. Let’s adjust our code to add that functionality now:

MHinit();function MHinit() {
var switchWon = 0;
var stayWon = 0;
function runTest(switchOrStay) {
var doors = [{
name: 'Door 1',
winner: false,
picked: false,
open: false
}, {
name: 'Door 2',
winner: false,
picked: false,
open: false
}, {
name: 'Door 3',
winner: false,
picked: false,
open: false
}];
var randomDoor = (x) => Math.floor(Math.random() * (x - 0) + 0);
doors[randomDoor(3)].winner = true;
doors[randomDoor(3)].picked = true;
var losingDoors = doors.filter(obj => (obj.winner === false));
var openNumber = randomDoor(losingDoors.length);
var openDoor = losingDoors[openNumber];
if (openDoor.picked == true) openDoor = losingDoors[+!openNumber];
openDoor.open = true;
if (switchOrStay) {
//switching if true
var switchWin = doors.filter(obj => (obj.picked === false && obj.open === false && obj.winner === true));
(switchWin.length > 0) ? switchWon++ : false;
} else if (!switchOrStay) {
//staying
var stayWin = doors.filter(obj => (obj.picked === true && obj.open === false && obj.winner === true));
(stayWin.length > 0) ? stayWon++ : false;
}
}
}

It’s looking great so far. We are collecting all the data we need. Really all we have left to do is to run this a number of times(100) and invoke the MHinit function.

There are two practical ways of doing this. One is to use a simple for loop:

function MHinit() {
var switchWon = 0;
var stayWon = 0;
for(let i = 0; i < 100;i++){
runTest(true);
runTest(false);
}
console.log('---');
console.log(stayWon + '% of the time Stay Won');
console.log(switchWon + '% of the time switch won');
console.log('---');
function runTest(switchOrStay) {
...
}
}

The other is to use a functional approach:

function MHinit() {
var switchWon = 0;

var stayWon = 0;

NumToRun(100);
function NumToRun(x) {
runTest(true);
runTest(false);
if (x > 0) NumToRun(x - 1);
else {
console.log('---');
console.log(stayWon + '% of the time Stay Won');
console.log(switchWon + '% of the time switch won');
console.log('---');
}
}
function runTest(switchOrStay) {
...
}
}

Either way works just fine! If we then simply call our MHinit function we will get our results in the console:

A demonstration of our test running in codePen:

The Answer:

From the conditions that we replicated it is pretty clear for us all to see that, clearly, Answer B is correct:

Conclusion

I hope that this has helped you to see how visualizing, blueprinting, and attacking a problem with a little bit of JavaScript know-how can be incredibly beneficial to general problem-solving!

I just wanted to take a minute to state that, if you’re having trouble being the architect of an application or program, try not to worry so much. You can always change things when you get hit in the head with something that makes you think that you’ve made an error.

As a matter of fact this wasn’t my first build of this problem. My first build used a few incorrect Data Types and the iteration for the results required the number of tests to be an even number(embarrassing, really!)

The point is, don’t sell yourself short. You’re allowed mistakes so that you can learn from them!

--

--