Skip to main content
3 of 5
added 749 characters in body
henryn
  • 163
  • 5

Monty Hall simulation - very first OOP project

I mainly write code for data analysis so don't think about or use OOP at all on the day job. I thought I'd have a go at a simple Monty Hall simulation in an object-oriented style to figure out how it works - specifically I based it on this: https://www.reddit.com/r/dailyprogrammer/comments/n94io8/20210510_challenge_389_easy_the_monty_hall_problem/

Appreciate this project has been done to death but I just want to get a bit of feedback on whether I 'get' OOP or not. Having written a lot of code in a procedural style, I found it quite confusing. Note that the strategies in the strategise method are from the link above. If an unrecognised name is used, it just picks a second door randomly.

What I specifically struggled with are things like:

  • What should be an object? There were lots of things that could have been. Doors, maybe a game show host, maybe the prizes... in the end I kept it simple but that was only after a few iterations and getting stuck.
  • Am I using the classes/methods correctly when running the simulation? Would you surface more of the methods rather than using a 'wrapper' method like I've done with Contestant.play_the_game?
  • The strategise method is a bit of a mess. Is there a better way of implementing this that isn't just a load of if...else?

Any other thoughts/criticisms on general style and anything else are welcome - I don't generally code in Python either so this was all very new.

My class definitions are in a file called monthHall.py:

import random

class MontyHall:
    def __init__(self):
        '''The Monty Hall game! Three doors, 2 goats, one car.'''
        self.doors = {1: "goat", 2:  "goat", 3: "goat"}
        prize_door = random.randint(1, 3)
        self.doors[prize_door] = "car"

    def check_door(self, door_number):
        '''Checks what is behind a given door.'''
        return self.doors[door_number]

    def first_reveal(self, door_number):
        '''Takes a door number picked by the contestant and opens one of other doors with a goat.'''
        doors = [1, 2, 3]
        doors.remove(door_number)
        prizes = list(map(self.check_door, doors))
        if "car" in prizes:
            del doors[prizes.index("car")]
        door = random.choice(doors)
        return door

    def second_reveal(self, door_number):
        '''Takes the contestant's second pick and reveals their prize.'''
        prize = self.check_door(door_number)
        return prize

class Contestant:
    def __init__(self, name):
        '''A contestant to play the Monty Hall game.'''
        self.name = name
        self.wins = []
        self.second_door = 0
        if self.name in ["Alice", "Bob", "Frank", "Gina"]:
            self.first_door = 1
        else:
            self.first_door = random.choice([1, 2, 3])

    def strategise(self, doors):
        '''Picks a second door. If the contestant name is not recognised, it just picks randomly.'''
        if self.name in ["Alice", "Dave"]:
            self.second_door = self.first_door
        elif self.name in ["Bob", "Erin"]:
            self.switch_doors(doors)
        elif self.name == "Frank":
            if 2 in doors:
                self.second_door = 2
            else:
                self.second_door = self.first_door
        elif self.name == "Gina":
            if not self.wins:
                self.second_door = self.first_door
            else:
                if self.wins[-1]:
                    if self.second_door != self.first_door:
                        self.switch_doors(doors)
                else:
                    if self.second_door != self.first_door:
                        self.second_door = self.first_door
                    else:
                        self.switch_doors(doors)
        else:
            self.second_door = random.choice(doors)

    def switch_doors(self, doors):
        '''Switches doors for the second contestant pick.'''
        doors.remove(self.first_door)
        self.second_door = doors[0]

    def first_pick(self, competition):
        '''Pass the first door choice to the competition to open one of the other doors, and then pick a strategy.'''
        open_door = competition.first_reveal(self.first_door)
        doors = [1, 2, 3]
        doors.remove(open_door)
        self.strategise(doors)

    def second_pick(self, competition):
        '''Pass the second door choice to the competition to reveal the prize!'''
        prize = competition.second_reveal(self.second_door)
        if (prize == "car"):
            self.wins.append(1)
        else:
            self.wins.append(0)

    def play_the_game(self, competition):
        '''Play the game.'''
        self.first_pick(competition)
        self.second_pick(competition)

And I run the simulation like this:

from montyHall import *

def main():
    alice = Contestant("Alice")
    bob = Contestant("Bob")
    carol = Contestant("Carol")
    dave = Contestant("Dave")
    erin = Contestant("Erin")
    frank = Contestant("Frank")
    gina = Contestant("Gina")

    for i in range(1000):
        for contestant in [alice, bob, carol, dave, erin, frank, gina]:
            competition = MontyHall()
            contestant.play_the_game(competition)

    for contestant in [alice, bob, carol, dave, erin, frank, gina]:
        print(contestant.name + " win rate: " + str(100 * sum(contestant.wins)/len(contestant.wins)) + "%")

if __name__ == "__main__":
    main()
henryn
  • 163
  • 5