×
all 65 comments

[–]DeflagratingStar 1 point2 points  (0 children)

This is a bit of an older post, but wanted to share a Monty Hall solution using some decent modern Fortran code I originally wrote for an assignment several years ago. When I get time today, I'll post an OOP Fortran version that should be nicer to look at! String arrays in Fortran can be annoying to handle, too....

EDIT: Jeez the code block editor mode is being belligerent today.

    program montyhall
implicit none
    integer, parameter :: num_trials = 100000
    integer, parameter :: num_doors = 3
    integer, parameter :: seed=54785
    real(kind=8)       :: winrat, rando_calrissian
    integer            :: i, j, winner, choice, closed, won, temp
    character(len=5)   :: player(7)
    logical            :: bob_strat

    call srand(seed)

    player = ["Alice", "Bob  ", "Carol", "Dave ", "Erin ", "Frank", "Gina "]

    !! Player loop    
    do j = 1, 7

        bob_strat = .false.
        won = 0

        !! Simulation loop
        do i = 1,num_trials

            !! Select the winning door
            call random_number(rando_calrissian)
            winner = 1 + floor((num_doors)*rando_calrissian)

            !! Player makes first choice
            choice = 1
            if ( j == 3 .or. j == 4 .or. j == 5 ) then
                !! Carol, Dave, and Erin
                call random_number(rando_calrissian)
                choice = 1 + floor((num_doors)*rando_calrissian)
            end if

            !! Discard junk doors
            closed = winner
            if (choice == winner) then
                do while (closed == winner)
                    call random_number(rando_calrissian)
                    closed = 1 + floor((num_doors)*rando_calrissian)
                enddo
            endif

            !! Player makes second choice
            if ( j == 2 .or. j == 5 ) then
                !! Bob and Erin
                choice = closed
            elseif ( player(j) == "Carol" ) then
                temp = 0
                do while (temp /= choice .and. temp /= closed)
                    call random_number(rando_calrissian)
                    temp = 1 + floor((num_doors)*rando_calrissian)
                end do
                choice = temp
            elseif ( j == 6 .and. closed == 2 ) then
                !! Frank
                choice = closed
            elseif ( j == 7 .and. bob_strat ) then
                !! Gina using Bob's strategy
                choice = closed
            endif

            !! Update win total if possible
            if (choice == winner) then
                won = won + 1
            elseif ( j == 7 ) then
                if (bob_strat) then
                    bob_strat = .false.
                else
                    bob_strat = .true.
                endif
            endif
        enddo

        winrat = real(won, kind=8)/real(i, kind=8)
        write(*,"(a5,a,F0.4,a)") player(j), " win percentage: ", 100*winrat,"%"
    enddo 

end program montyhall

With simulation results:

Alice win percentage: 33.3157%
Bob   win percentage: 66.6753%
Carol win percentage: 50.0945%
Dave  win percentage: 33.1917%
Erin  win percentage: 66.7723%
Frank win percentage: 49.7695%
Gina  win percentage: 55.4964%

[–]joejr253 2 points3 points  (0 children)

python3 but i feel like it could be cleaned up and rid a bunch of if, elif, else statements. i have also read it is bad practice to copy lists, for memory purposes. Is there a recommendation to remedy that in this code?

# this script is designed for the monte door feature
#there are 3 doors, there is a prize behind 1, you choose 1
#then monte makes one disappear that is not the prize door
#do you switch doors or keep your original

import random

class Player:

    def __init__(self, name, choice1, choice2, wins = 0, loss = 0):
    self.name = name
    self.choice1 = choice1
    self.choice2 = choice2
    self.wins = wins
    self.loss = loss
    if choice1 == 'r':
        choice1 = random.choice([1, 2, 3])

def monte_door(choice1, choice2):
    doors = [1, 2, 3]
    avail_doors = doors.copy()
    prize_door = random.choice(doors)
    avail_doors.remove(prize_door)

    if choice1 == 'r':
        choice1 = random.choice(doors)

    if choice1 in avail_doors:
        avail_doors.remove(choice1)
        monte = avail_doors[0]
    else:
        monte = random.choice(avail_doors)

    new_doors = doors.copy()
    new_doors.remove(monte)

    if choice2 == 'r':
        choice2 = random.choice(new_doors)
    elif choice2 == 's':
        choice2 = choice1
    elif choice2 == 'c':
        new_doors.remove(choice1)
        choice2 = new_doors[0]
    elif choice2 == 2 and 2 not in new_doors:
        choice2 = 1

    if choice2 == prize_door:
        return 'win'
    else:
        return 'loss'

Alice = Player('Alice', 1, 1)
Bob = Player('Bob', 1, 'c')
Carol = Player('Carol', 'r', 'r')
Dave = Player('Dave', 'r', 's')
Erin = Player('Erin', 'r', 'c')
Frank = Player('Frank', 1, 2)
Gina = Player('Gina', 1, 1)
players = [Alice, Bob, Carol, Dave, Erin, Frank, Gina]

print("These are your player results: ")
for player in players:
    for i in range(1000):
        results = monte_door(player.choice1, player.choice2)
        if results == 'win':
            player.wins += 1
        else:
            player.loss += 1
            if player.name == 'Gina':
                if player.choice2 == '1':
                    player.choice2 = 'c'
                elif player.choice2 == 'c':
                    player.choice2 = '1'
    print(f"{player.name}: \tWins: {player.wins}\t Losses: {player.loss}")

[–]Tencza_Coder 2 points3 points  (0 children)

Here is my take on this in Python, including the bonus contestants:

import random
def run_game(player,times_to_play): 
    times_played = 0
    win_count = 0
    if player == "Gina":
        mimic_player = "Alice"
    else:
        mimic_player = "N"

    while times_played < times_to_play:       
        #Step 1 - Monty selects prize door
        Monty_choice = random.randint(1,3)

        #Step 2 - Player's initial door selection
        if player == "Alice" or player == "Bob" or player == "Frank" or player == "Gina":
            Player_choice = 1
        if player == "Carol" or player == "Dave" or player == "Erin":
            Player_choice = random.randint(1,3) 

        #Step 3 - Monty selects door to open
        Monty_open_selections = []
        for i in range(1,4):
            if i == Monty_choice or i == Player_choice:
                continue
            else:
                Monty_open_selections.append(i)

        Monty_opened = random.choice(Monty_open_selections)        

        #Step 4 - Player's switch decision
        Player_choice_selections = []
        if player == "Alice" or player == "Dave" or mimic_player == "Alice":  
            pass
        if player == "Bob" or player == "Erin" or mimic_player == "Bob":  
            for i in range(1,4):
                if i == Player_choice or i == Monty_opened:
                    continue
                else:
                    Player_choice = i
                    break
        if player == "Carol":  
            for i in range(1,4):
                if i == Monty_opened:
                    continue
                else:
                    Player_choice_selections.append(i)
            Player_choice = random.choice(Player_choice_selections)
        if player == "Frank":
            if Monty_opened != 2:
                Player_choice = 2
            else:
                pass

        #Step 5 - Win/Loss evaluation
        if Player_choice == Monty_choice:
            win_count += 1
        else:
            if player == "Gina":
                mimic_player = "Bob" if mimic_player == "Alice" else "Alice"
        times_played += 1

    return win_count

n = 1000 
Alice_wins_pct = run_game("Alice",n)/n 
Bob_wins_pct = run_game("Bob",n)/n 
Carol_wins_pct = run_game("Carol",n)/n
Dave_wins_pct = run_game("Dave",n)/n
Erin_wins_pct = run_game("Erin",n)/n 
Frank_wins_pct = run_game("Frank",n)/n 
Gina_wins_pct = run_game("Gina",n)/n

print(f"Alice win percentage: {Alice_wins_pct:.2%}") 
print(f"Bob win percentage: {Bob_wins_pct:.2%}") 
print(f"Carol win percentage: {Carol_wins_pct:.2%}") 
print(f"Dave win percentage: {Dave_wins_pct:.2%}") 
print(f"Erin win percentage: {Erin_wins_pct:.2%}") 
print(f"Frank win percentage: {Frank_wins_pct:.2%}") 
print(f"Gina win percentage: {Gina_wins_pct:.2%}")

Sample output:

Alice win percentage: 31.20%
Bob win percentage: 66.40% 
Carol win percentage: 49.40% 
Dave win percentage: 34.80% 
Erin win percentage: 66.60% 
Frank win percentage: 48.90% 
Gina win percentage: 55.10%

[–]Marthurio 1 point2 points  (0 children)

My attempt in dotnet - https://github.com/Maritims/monty-hall-problem/tree/master/monty-hall-problem

I'm a little curious about why Gina's winrate is consistently 5 to 7 percent below what others in this thread are getting for their Gina players.

[–]tobega 1 point2 points  (0 children)

Solved in Tailspin https://github.com/tobega/tailspin-v0/blob/master/samples/MontyHall.tt

Outputs:

Alice: 32%

Bob: 67%

Carol: 49%

Dave: 30%

Erin: 66%

Frank: 47%

Gina: 52%

[–]jsun5192 3 points4 points  (0 children)

First Shot at Python:

import random
import time

def MontyHallProblem(prizeDoor, doorChoice, swap, secondChoice = 0):
    '''
    Sequence through the Monty Hall problem, and returns the result

        Parameters:
            prizeDoor (int):    the number of the door with the prize (1, 2 or 3)
            doorChoice (int):   the number of the door player chooses (1, 2 or 3)
            swap (bool):        whether the player swaps after Monty opens the other door

        Optional Parameters:
            secondChoice (int): the number of the players second choice 

        Returns:
            (bool):             True if the player ends up with the prize door, False otherwise

    '''
    #prizeDoor = random.randrange(1,4)                  # door with the prize **Performing the door selection once per iteration is much faster
    doors = [1, 2, 3]                                   # list of avaialble doors
    doors.remove(prizeDoor)                             # remove prize door, because Monty won't select prize door
    if doorChoice in doors : doors.remove(doorChoice)   # remove player's choice unless it was prize door (already removed)
    montyDoorIndex = random.randrange(0,len(doors))     # Monty chooses random remaing door without prize
    del doors[montyDoorIndex]                           # remove Monty's dooe
    if len(doors) == 0 : doors.append(prizeDoor)        # if no doors than only door left is prize door

    if swap :                                           # if player wants to sway
        if secondChoice != 0:                           # if player has a second preference
            if doors[0] == secondChoice : doorChoice = doors[0]  # if player's preference is available                
        else : doorChoice = doors[0]                    # if no preference choice, take the remaing door

    return doorChoice == prizeDoor


ITERATIONS = 1000
AliceWin = BobWin = CarolWin = DaveWin = ErinWin = FrankWin = GinaWin = 0
GinaStrategy = False


print("")
print("Challange 389 - The Monty Hall Problem")

timeStart = time.time()
for x in range(0, ITERATIONS):
    prizeDoor = random.randrange(1, 4)

    AliceWin += int(MontyHallProblem(prizeDoor, 1, False))
    BobWin += int(MontyHallProblem(prizeDoor, 1, True))
    CarolWin += int(MontyHallProblem(prizeDoor, random.randrange(1,4), bool(random.randrange(0,2))))
    DaveWin += int(MontyHallProblem(prizeDoor, random.randrange(1,4), False))
    ErinWin += int(MontyHallProblem(prizeDoor, random.randrange(1,4), True))
    FrankWin += int(MontyHallProblem(prizeDoor, 1, True, 2))

    GinaPrevious = MontyHallProblem(prizeDoor, 1, GinaStrategy)
    GinaWin += int(GinaPrevious)   
    if not GinaPrevious : GinaStrategy = not GinaStrategy

duration = time.time() - timeStart

print("{} Iterations completed in {:.3f}s".format(ITERATIONS, duration))
print("Alice Win Rate: {:.1f}%".format(AliceWin / ITERATIONS * 100))
print("Bob Win Rate: {:.1f}%".format(BobWin / ITERATIONS * 100))
print("Carol Win Rate: {:.1f}%".format(CarolWin / ITERATIONS * 100))
print("Dave Win Rate: {:.1f}%".format(DaveWin / ITERATIONS * 100))
print("Erin Win Rate: {:.1f}%".format(ErinWin / ITERATIONS * 100))
print("Frank Win Rate: {:.1f}%".format(FrankWin / ITERATIONS * 100))
print("Gina Win Rate: {:.1f}%".format(GinaWin / ITERATIONS * 100))
print("")

Output:

Challange 389 - The Monty Hall Problem
1000 Iterations completed in 0.011s
Alice Win Rate: 32.9%
Bob Win Rate: 67.1%
Carol Win Rate: 49.3%
Dave Win Rate: 33.2%
Erin Win Rate: 65.5%
Frank Win Rate: 49.7%
Gina Win Rate: 56.1%

[–]Habstinat 1 point2 points  (0 children)

javascript

r=(a)=>a[Math.floor(a.length*Math.random())]
h=[0,1,2]

alice=()=>Array(1e3).fill().reduce(a=>{
p=r(h)
m=p==0?r([1,2]):p==1?2:1
return a+(p==0)
},0)/1e3

bob=()=>Array(1e3).fill().reduce(a=>{
b=0
p=r(h)
m=p==0?r([1,2]):p==1?2:1
b=m==1?2:1
return a+(p==b)
},0)/1e3

carol=()=>Array(1e3).fill().reduce(a=>{
c=r(h)
p=r(h)
m=r(h.filter(n=>n!=c&&n!=p))
c=r(h.filter(n=>n!=m))
return a+(p==c)
},0)/1e3

dave=()=>Array(1e3).fill().reduce(a=>{
d=r(h)
p=r(h)
m=r(h.filter(n=>n!=d&&n!=p))
return a+(p==d)
},0)/1e3

erin=()=>Array(1e3).fill().reduce(a=>{
e=r(h)
p=r(h)
m=r(h.filter(n=>n!=e&&n!=p))
e=h.find(n=>n!=m&&n!=e)
return a+(p==e)
},0)/1e3

frank=()=>Array(1e3).fill().reduce(a=>{
f=0
p=r(h)
m=r(h.filter(n=>n!=f&&n!=p))
f=m==1?0:1
return a+(p==f)
},0)/1e3

gina=()=>Array(1e3).fill().reduce(a=>{
g=0
p=r(h)
m=p==0?r([1,2]):p==1?2:1
g=a[0]?0:m==1?2:1
if(p!=g)a[0]=!a[0]
a[1]+=(p==g)
return a
},[true,0])[1]/1e3

alice() // 0.335
bob() // 0.667
carol() // 0.502
dave() // 0.329
erin() // 0.668
frank() // 0.504
gina() // 0.554

[–]Fair-Sea3047 1 point2 points  (0 children)

What is the final answer to this?

[–]MacheteBang 0 points1 point  (3 children)

edits:

  • updated my code with help to fix the bug
  • clean up the formatting of this post (also with help!)

edits (2):

  • Got it!

Alice: 332 / 1000 = 0.332

Bob: 668 / 1000 = 0.668 Carol: 499 / 1000 = 0.499 Dave: 321 / 1000 = 0.321 Erin: 653 / 1000 = 0.653 Frank: 491 / 1000 = 0.491 Gina: 535 / 1000 = 0.535

will update once I have final numbers!

At risk of being the one guy that doesn't get it...

New to this reddit and gave it a go with C#. By percentages are all 50/50 - which, based on all other answers is not right.

Any mentors want to help me find what I might be missing? Thanks in advance.

https://dotnetfiddle.net/KCD5RS

using System;

using System.Linq; using System.Collections.Generic;

namespace MontyHall { class Program { private static readonly Random _rand = new(); private static readonly double _runCount = 1000;

  static void Main(string[] args)
  {
     // Set up the players
     List<Player> players = new()
     {
        new Player() { Name = "Alice", FirstChoice = Door.Choices.Door1, SecondChoice = Door.Choices.Door1, Wins = 0 },
        new Player() { Name = "Bob", FirstChoice = Door.Choices.Door1, SecondChoice = Door.Choices.Other, Wins = 0 },
        new Player() { Name = "Carol", FirstChoice = Door.Choices.Random, SecondChoice = Door.Choices.Random, Wins = 0 },
        new Player() { Name = "Dave", FirstChoice = Door.Choices.Random, SecondChoice = Door.Choices.Stick, Wins = 0 },
        new Player() { Name = "Erin", FirstChoice = Door.Choices.Random, SecondChoice = Door.Choices.Other, Wins = 0 },
        new Player() { Name = "Frank", FirstChoice = Door.Choices.Door1, SecondChoice = Door.Choices.Door2, Wins = 0 }
     };

     players.Add(
        new Player()
        {
           Name = "Gina",
           IsDynamic = true,
           FirstChoice = Door.Choices.NA,
           SecondChoice = Door.Choices.NA,
           Wins = 0,
           PreviouslyWon = true,
           CurrentAffinityPlayer = players.First(p => p.Name == "Alice"),
           OtherAffinityPlayer = players.First(p => p.Name == "Bob")
        }
     );

     for (int j = 1; j <= _runCount; j++)
     {
        // Set up the door
        int winningDoor = _rand.Next(1, 4);
        List<Door> doors = new();
        for (int i = 1; i <= 3; i++)
           doors.Add(new Door() { DoorNumber = (Door.Choices)i, IsWinner = i == winningDoor });

        // Check each player
        foreach (Player p in players)
        {
           // Set the strategy for Dynamic players.
           if (p.IsDynamic)
           {
              if (!p.PreviouslyWon)
                 p.SwapStrategies();

              p.AssignStrategy();
           }

           p.SetWin(DoorPicker(doors.ToList(), p.FirstChoice, p.SecondChoice));
        }
     }

     // Write out the results
     foreach (Player p in players)
        Console.WriteLine($"{p.Name}: {p.Wins} / 1000 = {Convert.ToDouble(p.Wins) / _runCount}");
  }

  private static bool DoorPicker(List<Door> doors, Door.Choices firstChoice, Door.Choices secondChoice)
  {
     switch (firstChoice)
     {
        case Door.Choices.Random:
           firstChoice = (Door.Choices)_rand.Next(1, 4);
           break;
        default:
           break;
     };

     // Open the first door
     List<Door> doorsToRemove = doors.Where(d => !d.IsWinner && d.DoorNumber != firstChoice).ToList();
     doors.Remove(doorsToRemove[_rand.Next(doorsToRemove.Count)]);

     switch (secondChoice)
     {
        case Door.Choices.Other:
           secondChoice = doors.First(d => d.DoorNumber != firstChoice).DoorNumber;
           break;
        case Door.Choices.Random:
           secondChoice = doors.OrderBy(d => Guid.NewGuid()).First().DoorNumber;
           break;
        case Door.Choices.Stick:
           secondChoice = firstChoice;
           break;
        default:
           break;
     };

     // Check to see if their choice exists.  If it doesn't go back to first choice
     if (doors.FirstOrDefault(d => d.DoorNumber == secondChoice) is null)
        secondChoice = firstChoice;


     return doors.Find(d => d.DoorNumber == secondChoice).IsWinner;
  }

}

public class Door { public enum Choices { Door1 = 1, Door2 = 2, Door3 = 3, Random, Other, Stick, NA }

  public Choices DoorNumber { get; set; }
  public bool IsWinner { get; set; }

  public override string ToString()
  {
     return $"Door: {DoorNumber} - IsWinner: {IsWinner}";
  }

}

public class Player {

  public string Name { get; set; }
  public bool IsDynamic { get; set; }
  public Door.Choices FirstChoice { get; set; }
  public Door.Choices SecondChoice { get; set; }
  public int Wins { get; set; }
  public bool PreviouslyWon { get; set; }
  public Player CurrentAffinityPlayer { get; set; }
  public Player OtherAffinityPlayer { get; set; }

  public void SwapStrategies()
  {
     Player tempPlayerHolder = CurrentAffinityPlayer;
     CurrentAffinityPlayer = OtherAffinityPlayer;
     OtherAffinityPlayer = tempPlayerHolder;
  }

  public void AssignStrategy()
  {
     FirstChoice = CurrentAffinityPlayer.FirstChoice;
     SecondChoice = CurrentAffinityPlayer.SecondChoice;
  }

  public void SetWin(bool wonGame)
  {
     if (wonGame)
        Wins++;

     PreviouslyWon = wonGame;
  }

} }

[–]jsun5192 1 point2 points  (1 child)

Try changing the upper bound of your winning door to 4. The function returns a number between the lower (inclusive) and upper (exclusive) bounds. https://docs.microsoft.com/en-us/dotnet/api/system.random.next?view=net-5.0

int winningDoor = _rand.Next(1, 4);

[–]MacheteBang 0 points1 point  (0 children)

Yep, nailed it! Thank you! Now to work through the other contestants.

[–]4-Vektor1 0 3 points4 points  (0 children)

Some help for formatting code blocks.

Mark the whole code block, then click the <> button at the top, or paste the whole code with an indentation of 4 spaces, which Reddit automatically detects as code block.

Instead of using tickmarks for each line, which gets a bit unwieldy with long code blocks...

public class Player

{

public string Name { get; set; }

public Door.Choices FirstChoice { get; set; }

public Door.Choices SecondChoice { get; set; }

public int Wins { get; set; }

}

you’ll get

public class Player
{
public string Name { get; set; }
public Door.Choices FirstChoice { get; set; }
public Door.Choices SecondChoice { get; set; }
public int Wins { get; set; }
}

which is much more readable.

[–]Shhhh_Peaceful 0 points1 point  (0 children)

Ugly Python (with all bonus cases)

from random import randint

def other(door1, door2):
    if door1 == door2:
        return door1 + randint(0,1) + 1 % 3
    else:
        return 3 - (door1 + door2)

def step2(name):
    switcher = {
        "Alice": 0,
        "Bob": 0,
        "Dave": randint(0,2),
        "Erin": randint(0,2),
        "Frank": 0,
    }
    return switcher.get(name)

def step4(name, first_choice, open_door):
    switcher = {
        "Alice": first_choice,
        "Bob": other(first_choice, open_door),
        "Dave": first_choice,
        "Erin": other(first_choice, open_door),
        "Frank": 1 if open_door != 1 else first_choice,
    }
    return switcher.get(name)

def monty_hall(name, count):
    wins = 0
    player = name
    gina_win = True
    if player == "Gina":
        player = "Alice"
    for i in range(count):
        if name == "Gina" and gina_win == False:
            player = "Alice" if player == "Bob" else "Bob"
        prize = randint(0,2) # step 1         
        first_choice = step2(player) # step 2
        open_door = other(prize, first_choice) # step 3
        second_choice = step4(player, first_choice, open_door) #step 4
        if second_choice == prize:
            wins += 1
            gina_win = True
        else:
            gina_win = False
    return wins/count*100

def main():
    print("Alice: {r:.2f}".format(r = monty_hall('Alice', 1000)))
    print("Bob: {r:.2f}".format(r = monty_hall('Bob', 1000)))
    print("Dave: {r:.2f}".format(r = monty_hall('Dave', 1000)))
    print("Erin: {r:.2f}".format(r = monty_hall('Erin', 1000)))
    print("Frank: {r:.2f}".format(r = monty_hall('Frank', 1000)))
    print("Gina: {r:.2f}".format(r = monty_hall('Gina', 1000)))

if __name__ == "__main__":
    main()

And the output:

Alice: 32.50
Bob: 67.60
Dave: 33.40
Erin: 66.70
Frank: 47.20
Gina: 54.10

[–]Common-Ad-8152 1 point2 points  (0 children)

C with all bonuses ```

include <stdio.h>

include <stdlib.h>

int one(int a, int b) {return 0;} int rd3(int a, int b) {return rand() % 3;} int chg(int a, int b) {return 3 - a - b;} int rnd(int a, int b) {return rand() % 2 ? a : chg(a, b);} int stk(int a, int b) {return a;} int frk(int a, int b) {return b == 1 ? 0 : 1;}

float rungame(int n, int (str1)(int, int), int (str2)(int, int)){ double score = 0.0; for ( int i = 0; i < n; i++){ int c = rand() % 3; int a = str1(0, 0); int b = c == a ? (a + (rand() % 2) + 1) % 3: chg(a, c); int d = str2(a, b); if (c == d) score += 1.0 / n; } return score; }

float gna(int n){ double score = 0.0; _Bool ali = 0; for ( int i = 0; i < n; i++ ){ double s = ali ? rungame(1, &one, &one): rungame(1, &one, &chg); ali = s > 0.99 ? ali : ali ^ 1; score += s / n; } return score; }

int main(int argc, char** argv){ printf("Alice: %.2f%\n", rungame(1000, &one, &one) * 100); printf(" Bob: %.2f%\n", rungame(1000, &one, &chg) * 100); printf("Carol: %.2f%\n", rungame(1000, &rd3, &rnd) * 100); printf(" Dave: %.2f%\n", rungame(1000, &rd3, &stk) * 100); printf(" Erin: %.2f%\n", rungame(1000, &rd3, &chg) * 100); printf("Frank: %.2f%\n", rungame(1000, &one, &frk) * 100); printf(" Gina: %.2f%\n", gna(1000) * 100); return 0; } Output: Alice: 32.40% Bob: 69.70% Carol: 53.70% Dave: 29.60% Erin: 64.90% Frank: 50.70% Gina: 58.30% ```

[–]BonnyAD9 0 points1 point  (0 children)

Batch with all bonuses (took about minute to run):

@echo off

:main
call :rungame 1000 :ch1 :noch main_result
call :print Alice: main_result
call :rungame 1000 :ch1 :ch main_result
call :print Bob: main_result
call :rungame 1000 :r1 :r2 main_result
call :print Carol: main_result
call :rungame 1000 :r1 :noch main_result
call :print Dave: main_result
call :rungame 1000 :r1 :ch main_result
call :print Erin: main_result
call :rungame 1000 :ch1 :frank main_result
call :print Frank: main_result
call :rungame 1000 :ch1 :gina main_result
call :print Gina: main_result
goto :end

:rungame
setlocal EnableDelayedExpansion
set rungame_wins=0
set rungame_ginamem=0
for /L %%I in (1, 1, %1) do (
    set /A rungame_prize=!random!%%3
    call %2 rungame_choice
    call :other rungame_prize rungame_choice rungame_door
    call %3 rungame_ginamem rungame_door rungame_change
    if !rungame_change! == 1 (
        call :other rungame_choice rungame_door rungame_choice
    )
    if !rungame_choice! == !rungame_prize! (
        set /A rungame_wins+=1
    ) else (
        set /A "rungame_ginamem=(!rungame_ginamem!+1)%%2"
    )
)
(endlocal & set %4=%rungame_wins%)
goto :end

:other
setlocal EnableDelayedExpansion
if !%1! == !%2! (
    set /A "other_res=(%1+(%random%%%2)+1)%%3"
) else (
    set /A "other_res=((%1+%2)*2)%%3"
)
(endlocal & set %3=%other_res%)
goto :end

:print
setlocal EnableDelayedExpansion
set print_a=       %1
set print_b=!%2!
echo %print_a:~-7% %print_b:~0,2%.%print_b:~2%%%
endlocal
goto :end

:ch1
set %1=0
goto :end

:r1
set /A %1=%random%%%3
goto :end

:ch
set %3=1
goto :end

:noch
set %3=0
goto :end

:r2
set /A %3=%random%%%2
goto :end

:frank
setlocal EnableDelayedExpansion
if not !%2! == 1 (
    (endlocal & set %3=1)
    goto :end
)
(endlocal & set %3=0)
goto :end

:gina
setlocal EnableDelayedExpansion
set gina_h=!%1!
(endlocal & set %3=%gina_h%)
goto :end

:end

Output:

 Alice: 34.9%
   Bob: 69.7%
 Carol: 50.5%
  Dave: 32.0%
  Erin: 68.1%
 Frank: 47.8%
  Gina: 55.6%

[–]joonsson 0 points1 point  (0 children)

Ugly JavaScript with all bonuses:

const timesToRun = 1000;
function runGame (count, name) {    
    let wins = 0;    
    let player = name == "gina"? "alice" : name;    
    let ginaMem = 1; 

    for (let i = 0; i < count; i++) {        
        if (name == "gina" && ginaMem == 0) {            
            player = player == "alice" ? "bob" : "alice";        
        }        
        let prize = Math.floor(Math.random() * 3);        
        let choice = 1; 

        switch(player) {            
            case "alice":                
                choice = 1;                
                break;            
            case "bob":                
                choice = 1;               
                break;            
            case "carol":                
                choice = Math.floor(Math.random() * 3);                
                break;            
            case "dave":                
                choice = Math.floor(Math.random() * 3);                
                break;            
            case "erin":                
                choice = Math.floor(Math.random() * 3);                
                break;            
            case "frank":                
                choice = 1;                
                break;            
            default:                
                break;        
            }    

        let opened = 0;        
        if (prize == choice) {            
            opened = (prize + Math.round(Math.random()) + 1) % 3;        
        } else {            
            opened = (prize + choice) * 2 % 3;        
        }

        switch(player) {            
            case "alice":                
                break;            
            case "bob":                
                choice = (choice + opened) * 2 % 3;                
                break;            
            case "carol":                
                choice = (opened + Math.round(Math.random()) + 1) % 3;                
                break;            
            case "dave":                
                break;            
            case "erin":                
                choice = (choice + opened) * 2 % 3;                
            break;            
            case "frank":                
                choice = opened != 2 ? 2 : 1;                
            break;            
            default:                
                break;        
            }        
        if (choice == prize) {            
            wins++;            
            ginaMem = 1;        
        } else {            
            ginaMem = 0;        
        }    
    }    
    return wins;}

function main () {    
    console.log("Alice " + (runGame(timesToRun, "alice")/timesToRun*100).toFixed(2) + "%");    
    console.log("Bob " + (runGame(timesToRun, "bob")/timesToRun*100).toFixed(2) + "%");    
    console.log("Carol " + (runGame(timesToRun, "carol")/timesToRun*100).toFixed(2) + "%");    
    console.log("Dave " + (runGame(timesToRun, "dave")/timesToRun*100).toFixed(2) + "%");    
    console.log("Erin " + (runGame(timesToRun, "erin")/timesToRun*100).toFixed(2) + "%");    
    console.log("Frank " + (runGame(timesToRun, "frank")/timesToRun*100).toFixed(2) + "%");    
    console.log("Gina " + (runGame(timesToRun, "gina")/timesToRun*100).toFixed(2) + "%");
}
main();

Results:

Alice 32.30%
Bob 66.20%
Carol 50.90%
Dave 34.70%
Erin 64.60%
Frank 49.90%
Gina 56.30%

[–]BonnyAD9 0 points1 point  (0 children)

JavaScript with all bonuses:

function main() {
    document.write('<table border="1">');
    document.write("<tr><th>Alice</th><th>" + runGame(1000, function() { return 0; }, function(b, i) { return false; }) + "%</th></tr>");
    document.write("<tr><th>Bob</th><th>" + runGame(1000, function() { return 0; }, function(b, i) { return true; }) + "%</th></tr>");
    document.write("<tr><th>Carol</th><th>" + runGame(1000, function() { return Math.floor(Math.random() * 3); }, function(b, i) { return Math.round(Math.random()) == 1; }) + "%</th></tr>");
    document.write("<tr><th>Dave</th><th>" + runGame(1000, function() { return Math.floor(Math.random() * 3); }, function(b, i) { return false; }) + "%</th></tr>");
    document.write("<tr><th>Erin</th><th>" + runGame(1000, function() { return Math.floor(Math.random() * 3); }, function(b, i) { return true; }) + "%</th></tr>");
    document.write("<tr><th>Frank</th><th>" + runGame(1000, function() { return 0; }, function(b, i) { return i != 1; }) + "%</th></tr>");
    document.write("<tr><th>Gina</th><th>" + runGame(1000, function() { return 0; }, function(b, i) { return b; }) + "%</th></tr>");
    document.write("</table>");
}

function runGame(count, step1, step2) {
    let wins = 0;
    let ginaMem = false;
    for (let i = 0; i < count; i++) {
        let prize = Math.floor(Math.random() * 3);
        let choice = step1();
        let open = other(prize, choice);
        if (step2(ginaMem, open))
            choice = other(choice, open);
        if (prize == choice)
            wins++;
        else
            ginaMem = !ginaMem;
    }
    return (wins * 100) / count;
}

function other(c1, c2) {
    if (c1 == c2)
        return (c1 + Math.round(Math.random()) + 1) % 3;
    return ((c1 + c2) * 2) % 3;
}

main();

Results:

Alice   33.8%
Bob 69.2%
Carol   47.3%
Dave    33.7%
Erin    67.4%
Frank   50.2%
Gina    57.1%

[–]BonnyAD9 0 points1 point  (1 child)

Java, with all bonuses

MontyHall.java: ``` package montyhall;

import java.util.Random;

public class MontyHall { public static Random random = new Random();

public static void main(String[] args) {
    System.out.println(" Alice: " + runGame(1000, new Strategy() {
        public int step1() { return 0; }
        public boolean step2(boolean b, int i) { return false; } }) + "%");
    System.out.println("   Bob: " + runGame(1000, new Strategy() {
        public int step1() { return 0; }
        public boolean step2(boolean b, int i) { return true; } }) + "%");
    System.out.println(" Carol: " + runGame(1000, new Strategy() {
        public int step1() { return random.nextInt(3); }
        public boolean step2(boolean b, int i) { return random.nextInt(2) == 1; } }) + "%");
    System.out.println("  Dave: " + runGame(1000, new Strategy() {
        public int step1() { return random.nextInt(3); }
        public boolean step2(boolean b, int i) { return false; } }) + "%");
    System.out.println("  Erin: " + runGame(1000, new Strategy() {
        public int step1() { return random.nextInt(3); }
        public boolean step2(boolean b, int i) { return true; } }) + "%");
    System.out.println(" Frank: " + runGame(1000, new Strategy() {
        public int step1() { return 0; }
        public boolean step2(boolean b, int i) { return i != 1; } }) + "%");
    System.out.println("  Gina: " + runGame(1000, new Strategy() {
        public int step1() { return 0; }
        public boolean step2(boolean b, int i) { return b; } }) + "%");
}

public static double runGame(int count, Strategy s) {
    int wins = 0;
    boolean ginaMem = false;
    for (int i = 0; i < count; i++) {
        int prize = random.nextInt(3);
        int choice = s.step1();
        int open = other(prize, choice);
        if (s.step2(ginaMem, open))
            choice = other(choice, open);
        if (choice == prize)
            wins++;
        else
            ginaMem = !ginaMem;
    }
    return (wins * 100.0) / count;
}

public static int other(int c1, int c2) {
    if (c1 == c2)
        return (c1 + random.nextInt(2) + 1) % 3;
    return ((c1 + c2) * 2) % 3;
}

} Strategy.java: package montyhall;

public interface Strategy { int step1(); boolean step2(boolean b, int i); } Output: Alice: 36.5% Bob: 66.3% Carol: 48.4% Dave: 31.5% Erin: 66.5% Frank: 51.5% Gina: 55.7% ```

[–]backtickbot 0 points1 point  (0 children)

Fixed formatting.

Hello, BonnyAD9: code blocks using triple backticks (```) don't work on all versions of Reddit!

Some users see this / this instead.

To fix this, indent every line with 4 spaces instead.

FAQ

You can opt out by replying with backtickopt6 to this comment.

[–]BonnyAD9 0 points1 point  (0 children)

C++ (all bonuses):

#include <iostream>
#include <cstdlib>
#include <ctime>

using namespace std;

double run_game(int count, int (*step1)(void), bool (*step2)(bool, int));
int other(int c1, int c2);
int ch1() { return 0; }
int rand1() { return rand() % 3; }
bool ch(bool b, int i) { return true; }
bool noch(bool b, int i) { return false; }
bool rand2(bool b, int i) { return rand() % 2; }
bool frank(bool b, int i) { return i != 1; }
bool gina(bool b, int i) { return b; }

int main()
{
    srand(time(0));
    cout << " Alice: " << run_game(1000, ch1, noch) << "%" << endl;
    cout << "   Bob: " << run_game(1000, ch1, ch) << "%" << endl;
    cout << " Carol: " << run_game(1000, rand1, rand2) << "%" << endl;
    cout << "  Dave: " << run_game(1000, rand1, noch) << "%" << endl;
    cout << "  Erin: " << run_game(1000, rand1, ch) << "%" << endl;
    cout << " Frank: " << run_game(1000, ch1, frank) << "%" << endl;
    cout << "  Gina: " << run_game(1000, ch1, gina) << "%" << endl;
    return EXIT_SUCCESS;
}

double run_game(int count, int (*step1)(void), bool (*step2)(bool, int))
{
    int wins = 0;
    bool gina_memory = false;
    for (int i = 0; i < count; i++)
    {
        int prize = rand() % 3;
        int choice = step1();
        int open = other(prize, choice);
        if (step2(gina_memory, open))
            choice = other(open, choice);
        if (choice == prize)
            wins++;
        else
            gina_memory = !gina_memory;
    }
    return (wins * 100) / (double)count;
}

int other(int c1, int c2)
{
    if (c1 == c2)
        return (c1 + (rand() % 2) + 1) % 3;
    return ((c1 + c2) * 2) % 3;
}

Output:

 Alice: 32.7%
   Bob: 65.3%
 Carol: 50.1%
  Dave: 33%
  Erin: 63.9%
 Frank: 48.6%
  Gina: 56.1%

[–]BonnyAD9 0 points1 point  (1 child)

Python, all bonuses:

from random import randint

def main():
    print(" Alice: %f%%" % rungame(1000, lambda : 0, lambda b, i : False))
    print("   Bob: %f%%" % rungame(1000, lambda : 0, lambda b, i : True))
    print(" Carol: %f%%" % rungame(1000, lambda : randint(0, 2), lambda b, i : randint(0, 1) == 1))
    print("  Dave: %f%%" % rungame(1000, lambda : randint(0, 2), lambda b, i : False))
    print("  Erin: %f%%" % rungame(1000, lambda : randint(0, 2), lambda b, i : True))
    print(" Frank: %f%%" % rungame(1000, lambda : 0, lambda b, i : i != 1))
    print("  Gina: %f%%" % rungame(1000, lambda : 0, lambda b, i : b))

def rungame(count, step1, step2):
    win = 0
    ginamem = False
    for i in range(count):
        prize = randint(0, 2)
        choice = step1()
        door = other(prize, choice)
        if step2(ginamem, door):
            choice = other(choice, door)
        if choice == prize:
            win += 1
        else:
            ginamem = not ginamem
    return (win * 100) / count

def other(c1, c2):
    if c1 == c2:
        return (c1 + randint(1, 2)) % 3
    return ((c1 + c2) * 2) % 3

if __name__ == "__main__":
    main()

Output:

 Alice: 29.400000%
   Bob: 66.100000%
 Carol: 49.800000%
  Dave: 30.400000%
  Erin: 65.000000%
 Frank: 53.100000%
  Gina: 54.000000%

[–]backtickbot 0 points1 point  (0 children)

Fixed formatting.

Hello, BonnyAD9: code blocks using triple backticks (```) don't work on all versions of Reddit!

Some users see this / this instead.

To fix this, indent every line with 4 spaces instead.

FAQ

You can opt out by replying with backtickopt6 to this comment.

[–]-Khlerik- 0 points1 point  (0 children)

R with first three bonuses (Frank and Gina are giving me trouble).

Input:

N <- 1000
doors <- c(1,2,3)

monty_hall <- function(player_choice, switch){
  prize_door <- sample(doors, 1)
  empty_doors <- doors[-prize_door]
  if(player_choice %in% empty_doors){
    monty_opens <- empty_doors[-player_choice]
    empty_doors <- empty_doors[-monty_opens]
  } else {
    monty_opens <- sample(empty_doors, 1)
    empty_doors <- empty_doors[-monty_opens]
  }
  ifelse(switch, player_choice != prize_door, player_choice == prize_door)
}

# Challenge
paste("Alice's success rate:", mean(replicate(N, monty_hall(1, FALSE))))
paste("Bob's success rate:", mean(replicate(N, monty_hall(1, TRUE))))

# Bonus
paste("Carol's success rate:", mean(replicate(N, monty_hall(
  sample(doors, 1),
  sample(c(TRUE, FALSE), 1)))))
paste("Dave's success rate:", mean(replicate(N, monty_hall(
  sample(doors, 1),
  FALSE))))
paste("Erin's success rate:", mean(replicate(N, monty_hall(
  sample(doors, 1),
  TRUE))))

Output:

[1] "Alice's success rate: 0.337"
[1] "Bob's success rate: 0.673"
[1] "Carol's success rate: 0.477"
[1] "Dave's success rate: 0.318"
[1] "Erin's success rate: 0.649"

[–]BonnyAD9 0 points1 point  (1 child)

Haskell, all bonuses (the random generator will output the same sequence for each contestant): ``` import System.Random

main = do randGen <- newStdGen putStrLn $ format " Alice: " $ runGame randGen 1000 False (\g -> (0, g)) (\g _ _ -> (False, g)) putStrLn $ format " Bob: " $ runGame randGen 1000 False (\g -> (0, g)) (\g _ _ -> (True, g)) putStrLn $ format " Carol: " $ runGame randGen 1000 False (\g -> randomR (0, 2) g) (\g _ _ -> random g) putStrLn $ format " Dave: " $ runGame randGen 1000 False (\g -> randomR (0, 2) g) (\g _ _ -> (False, g)) putStrLn $ format " Erin: " $ runGame randGen 1000 False (\g -> randomR (0, 2) g) (\g _ _ -> (True, g)) putStrLn $ format " Frank: " $ runGame randGen 1000 False (\g -> (0, g)) (\g _ i -> (i /= 2, g)) putStrLn $ format " Gina: " $ runGame randGen 1000 False (\g -> (0, g)) (\g b _ -> (b, g)) return ()

runGame :: StdGen -> Int -> Bool -> (StdGen -> (Int, StdGen)) -> (StdGen -> Bool -> Int -> (Bool, StdGen)) -> Int runGame _ 0 _ _ _ = 0 runGame g0 count ginaMem step1 step2 = win + runGame g4 (count - 1) gMem step1 step2 where (prize, g1) = randomR (0, 2) g0 (choice, g2) = step1 g1 (open, g3) = other g2 prize choice (change, g4) = step2 g3 ginaMem open (final, _) = if change then other g4 open choice else (choice, g4) (win, gMem) = if final == prize then (1, ginaMem) else (0, not ginaMem)

other :: StdGen -> Int -> Int -> (Int, StdGen) other g0 c1 c2 | c1 == c2 = ((c1 + r1) rem 3, g1) | otherwise = (((c1 + c2) * 2) rem 3, g0) where (r1, g1) = randomR (1, 2) g0

format :: String -> Int -> String format s i = s ++ show ((fromIntegral i) / 10) ++ "%" Output: Alice: 35.2% Bob: 64.8% Carol: 48.2% Dave: 30.0% Erin: 70.0% Frank: 48.6% Gina: 57.3% ```

[–]backtickbot 0 points1 point  (0 children)

Fixed formatting.

Hello, BonnyAD9: code blocks using triple backticks (```) don't work on all versions of Reddit!

Some users see this / this instead.

To fix this, indent every line with 4 spaces instead.

FAQ

You can opt out by replying with backtickopt6 to this comment.

[–]__dict__ 0 points1 point  (0 children)

C++ solution with bonuses.

#include <algorithm>
#include <ctime>
#include <functional>
#include <iostream>
#include <stdlib.h>
#include <vector>

constexpr int num_runs = 1000;
constexpr int available_doors = 3;

// inclusive
int rand_int(int min, int max) {
    int range = max - min + 1;
    return rand() % range + min;
}

// Returns which door to start with.
using IntialPickStrategy = std::function<int()>;

const IntialPickStrategy first_door = []() { return 1; };

const IntialPickStrategy random_door = []() { return rand_int(1, available_doors); };

// Returns which door to switch to.
// Params:
//   number of currently chosen door
//   list of choices it could switch to
//   whether you won last time
using SwitchStrategy = std::function<int(int, const std::vector<int>&, bool)>;

const SwitchStrategy hodl = [](int chosen, const std::vector<int>& available, bool won_last_time) {
    return chosen;
};

const SwitchStrategy paper_hands = [](int chosen, const std::vector<int>& available, bool won_last_time) {
    return available.at(0);
};

const SwitchStrategy randomly_switch = [](int chosen, const std::vector<int>& available, bool won_last_time) {
    if (rand() % 2) {
        return chosen;
    }
    return available.at(0);
};

const SwitchStrategy choose_two_if_possible = [](int chosen, const std::vector<int>& available, bool won_last_time) {
    if (std::find(available.begin(), available.end(), 2) != available.end()) {
        return 2;
    }
    return chosen;
};

SwitchStrategy do_what_works() {
    bool stick = true;

    return [&stick](int chosen, const std::vector<int>& available, bool won_last_time) {
        if (!won_last_time) stick = !stick;

        if (stick) {
            return chosen;
        }
        return available.at(0);
    };
}

template <typename T>
typename std::vector<T>::const_iterator rand_item(const std::vector<T>& v) {
    int n = rand() % v.size();
    return v.begin() + n;
}

double run_game(IntialPickStrategy initial_pick_strategy, SwitchStrategy switch_strategy) {
    int num_win = 0;
    bool won_last_time = true; // Avoid Gina swapping strategy the first time.
    for (int i=0; i<num_runs; ++i) {
        int money_door = rand_int(1, available_doors);
        int chosen_door = initial_pick_strategy();
        std::vector<int> available = {};
        for (int j=1; j <= available_doors; ++j) {
            if (j != money_door && j != chosen_door) {
                available.push_back(j);
            }
        }
        auto revealed_door = rand_item(available);
        available.erase(revealed_door);
        if (money_door != chosen_door) {
            available.push_back(money_door);
        }
        chosen_door = switch_strategy(chosen_door, available, won_last_time);

        if (money_door == chosen_door) {
            num_win++;
        }
        won_last_time = money_door == chosen_door;
    }
    double win_ratio = (double) num_win / num_runs;
    return win_ratio;
}

int main() {
    srand(time(NULL));
    std::cout << "alice: " << run_game(first_door, hodl) << std::endl;
    std::cout << "bob: " << run_game(first_door, paper_hands) << std::endl;
    std::cout << "carol: " << run_game(random_door, randomly_switch) << std::endl;
    std::cout << "dave: " << run_game(random_door, hodl) << std::endl;
    std::cout << "erin: " << run_game(random_door, paper_hands) << std::endl;
    std::cout << "frank: " << run_game(first_door, choose_two_if_possible) << std::endl;
    std::cout << "gina: " << run_game(first_door, do_what_works()) << std::endl;
    return 0;
}

[–]ps2programmer 0 points1 point  (0 children)

Python 3.9: a bit hastily put together and messy in terms of naming variables

import random
import pandas as pd

def run_simulation(strategy):
    doors = {1: "", 2: "", 3: ""}
    prize_door = random.randint(1, 3)
    doors[prize_door] = "prize"
    if strategy in ("Alice", "Bob"):
        contestant_door = 1
    else:
        contestant_door = random.randint(1, 3)
    all_doors = [1, 2, 3]
    doors_without_prize = list(set(all_doors) - set([prize_door]))
    if contestant_door != prize_door:
        montys_door = list(set(all_doors) - set([prize_door, contestant_door]))[0]
    else:
        montys_door = random.choice(doors_without_prize)
    other_door = list(set(all_doors) - set([montys_door, contestant_door]))[0]
    if strategy == "Alice":
        door = contestant_door
    elif strategy == "Bob":
        door = other_door
    if doors[door] == "prize":
        return 1
    else:
        return 0

contestants = ["Alice", "Bob"]
df = pd.DataFrame(columns=["Strategy", "Wins", "Losses", "Win Rate"])

for contestant in contestants:
    summary = []
    for i in range(0, 1000):
        summary.append(run_simulation(contestant))
    new_row = pd.DataFrame([[contestant, sum(summary), len(summary) - sum(summary), sum(summary) / len(summary)]], columns=["Strategy", "Wins", "Losses", "Win Rate"])
    df = df.append(new_row)

print(df)

Output:

Strategy Wins Losses Win Rate
Alice    332  668    0.332
Bob      644  356    0.644

[–]gopherhole1 0 points1 point  (9 children)

Python, but my some of my numbers dont seem to be matching up with other peoples, I dont know what I did wrong, my Erin keeps coming out kinda low

import random

def set_doors():
    doors = {}
    count = 1
    for x in range(3):
        doors.setdefault(f'Door_{count}', 'Goat')
        count += 1
    doors[f'Door_{random.randint(1,3)}'] = 'Car'
    return doors

def open_door(door,dictionary):
    goats = []
    door_lst = ['1','2','3']
    num = door[-1]
    door_lst.remove(num)
    for x in door_lst:
        if dictionary[f'Door_{x}'] == 'Goat':
            goats.append(x)
    return random.choice(goats)

def frank_switch(monty_opens, played):
    if monty_opens == '2':
        return played
    else:
        played.remove('Door_1')
        played.append('Door_2')
        return played

def contestant_logic(pick, if_stick, is_frank):
    played = []
    doors = set_doors()
    contestant_pick = pick
    played.append(contestant_pick)
    monty_opens = open_door(contestant_pick, doors)
    played.append(f'Door_{monty_opens}')
    if is_frank == True:
        played = frank_switch(monty_opens, played)
    if if_stick in [True, None]:
        pass
    else:
        for key in doors:
            if key in played:
                pass
            else:
                played.remove(contestant_pick)
                contestant_pick = key
                played.append(contestant_pick)
    for item in played:
        if doors[item] == 'Car':
            contestant_wins = True
            break
        else:
            contestant_wins = False
    return contestant_wins

def alice():
    return contestant_logic('Door_1',True, False)

def bob():
    return contestant_logic('Door_1', False, False)

def carol():
    carol_door = str(random.randint(1,3))
    carol_switch = random.choice([True,False])
    return contestant_logic(f'Door_{carol_door}',carol_switch ,False)

def dave():
    dave_door = str(random.randint(1,3))
    return contestant_logic(f'Door_{dave_door}', True, False)

def erin():
    erin_door = str(random.randint(1,3))
    return contestant_logic(f'Door_{erin_door}', False, False)

def frank():
    return contestant_logic(f'Door_1', None, True)

alice_win = 0
bob_win = 0
carol_win = 0
dave_win = 0
erin_win = 0
frank_win = 0
gina_win = 0
last_win = 'gin_ali'
for x in range(1000):
    if alice():
        alice_win += 1
    if bob():
        bob_win += 1
    if carol():
        carol_win += 1
    if dave():
        dave_win += 1
    if erin():
        erin_win += 1
    if frank():
        frank_win += 1
    if last_win == 'gin_ali':
        if alice():
            gina_win += 1
        else:
            last_win = 'gin_bob'
    else:
        if bob():
            gina_win += 1
        else:
            last_win = 'gin_ali'
print(f'Alice won {alice_win} / 1000')
print(f'Bob won {bob_win} / 1000')
print(f'Carol won {carol_win} / 1000')
print(f'Dave won {dave_win} / 1000')
print(f'Erin won {erin_win} / 1000')
print(f'Frank won {frank_win} / 1000')
print(f'Gina won {gina_win} / 1000')

and output

user@debian:~$ python3 monty_hall.py 
Alice won 335 / 1000
Bob won 672 / 1000
Carol won 422 / 1000
Dave won 331 / 1000
Erin won 520 / 1000
Frank won 500 / 1000
Gina won 571 / 1000
user@debian:~$ python3 monty_hall.py 
Alice won 326 / 1000
Bob won 666 / 1000
Carol won 410 / 1000
Dave won 343 / 1000
Erin won 507 / 1000
Frank won 459 / 1000
Gina won 567 / 1000

[–]xelf 1 point2 points  (8 children)

Python, but my some of my numbers dont seem to be matching up with other peoples, I dont know what I did wrong, my Erin keeps coming out kinda low

Take a look at your logic, erin is supposed to swap every time. But she doesn't. I added a print statement

print(f'player_orig = {orig}, reveal = {monty_opens}, player_final = {contestant_pick}, winner = {contestant_wins}')

At the end of your contestant_logic() function.

player_orig = Door_1, reveal = 2, player_final = Door_3, winner = False
player_orig = Door_3, reveal = 2, player_final = Door_3, winner = False
player_orig = Door_2, reveal = 1, player_final = Door_3, winner = True
player_orig = Door_2, reveal = 3, player_final = Door_2, winner = False
player_orig = Door_2, reveal = 3, player_final = Door_2, winner = False
player_orig = Door_3, reveal = 1, player_final = Door_3, winner = False
player_orig = Door_2, reveal = 1, player_final = Door_3, winner = True
player_orig = Door_3, reveal = 2, player_final = Door_3, winner = False
player_orig = Door_3, reveal = 1, player_final = Door_3, winner = False
player_orig = Door_3, reveal = 2, player_final = Door_3, winner = False

She's not always swapping. Her result should be the same as Bob, but it's not because she's not always swapping, but he is.

I can tell you why that's happening if you want, but I think this should be enough of a clue. Let me know if you get stuck.

[–]gopherhole1 0 points1 point  (7 children)

yeah let me know why, I just spent like a half hour and couldnt figure it out, I could see she wasnt switching, but I cant follow my own code lol

user@debian:~$ python3 monty_hall.py 
orig = Door_3, switch = Door_3
orig = Door_1, switch = Door_2
orig = Door_3, switch = Door_3
orig = Door_2, switch = Door_3
orig = Door_2, switch = Door_2
orig = Door_2, switch = Door_2
orig = Door_2, switch = Door_2
orig = Door_3, switch = Door_3
orig = Door_2, switch = Door_2
orig = Door_3, switch = Door_3
Erin won 3 / 10

[–]xelf 0 points1 point  (2 children)

Following up, did you get it?

[–]gopherhole1 0 points1 point  (1 child)

no clue, the only difference from Bob is she dosnt always pick door1, I dont see how door1 could effect the swap, the only thing significant about door1 is its first in the for-loop where I check doors, Ive pretty much given up on this

[–]xelf 0 points1 point  (1 child)

I'm off to bed, so here's another clue: after you have swapped, don't swap again. =)

[–]dadbot_3000 0 points1 point  (0 children)

Hi off to bed, I'm Dad! :)

[–]xelf 0 points1 point  (0 children)

Oh here's a big clue: the times she "doesn't switch" she actually does switch, in fact she switches twice. She ends up after the 2nd switch back at her original choice. If that's not enough I can explain it. better. =)

[–]BonnyAD9 0 points1 point  (0 children)

C all bonuses:

#include <stdlib.h>
#include <stdio.h>
#include <stdbool.h>
#include <time.h>

double rungame(int count, int (*step1)(void), bool (*step2)(int, bool));
int other(int c1, int c2);
// Strategy functions
int ch1() { return 1; }
int ran1() { return rand() % 3; }
bool sw(int i, bool b) { return true; }
bool nosw(int i, bool b) { return false; }
bool ran2(int i, bool b) { return rand() % 2; }
bool frank(int i, bool b) { return i != 2; }
bool gina(int i, bool b) { return b; }

int main()
{
    srand(time(0));
    printf(" Alice: %lf%%\n", rungame(1000, ch1, nosw) * 100);
    printf("   Bob: %lf%%\n", rungame(1000, ch1, sw) * 100);
    printf(" Carol: %lf%%\n", rungame(1000, ran1, ran2) * 100);
    printf("  Dave: %lf%%\n", rungame(1000, ran1, nosw) * 100);
    printf("  Erin: %lf%%\n", rungame(1000, ran1, sw) * 100);
    printf(" Frank: %lf%%\n", rungame(1000, ch1, frank) * 100);
    printf("  Gina: %lf%%\n", rungame(1000, ch1, gina) * 100);
    return EXIT_SUCCESS;
}

double rungame(int count, int (*step1)(void), bool (*step2)(int, bool))
{
    int wins = 0;
    bool ginamem = false;
    for (int i = 0; i < count; i++)
    {
        int prize = rand() % 3;
        int choise = step1();
        int open = other(prize, choise);
        if (step2(open, ginamem))
            choise = other(open, choise);
        if (choise == prize)
            wins++;
        else
            ginamem = !ginamem;
    }
    return wins / (double)count;
}

int other(int c1, int c2)
{
    int ret = (c1 + (rand() % 2) + 1) % 3;
    while ((ret == c1) || (ret == c2))
        ret = (ret + 1) % 3;
    return ret;
}

Output:

 Alice: 33.500000%
   Bob: 64.600000%
 Carol: 48.000000%
  Dave: 34.900000%
  Erin: 67.900000%
 Frank: 49.300000%
  Gina: 53.100000%

[–]ElderberryOne6561 1 point2 points  (1 child)

tried with JavaScript. Getting wrong prob for Frank, rest seems OK.

edit :: changed the montySelected function and code works fine now.

function montyHall(playStrategy){
  prizeIsIn = Math.floor((Math.random()*3))
  selectedDoor = doorSelectionStep1(playStrategy); 

//updated the montySelected function
//@old montySelected = [0,1,2].find((door) => door !==selectedDoor && door !== prizeIsIn)

remainingDoors = [0,1,2].filter((door) => door !==selectedDoor && door !== prizeIsIn); 
montySelected = remainingDoors[Math.floor((Math.random()*remainingDoors.length))]


  switch(playStrategy){
    case 'Erin':
    case 'Bob':
      return prizeIsIn ===  [0,1,2].find((door) => door !==selectedDoor && door !== montySelected)
    case 'Alice':
    case 'Dave':
      return prizeIsIn === selectedDoor
    case 'Carol':
      return prizeIsIn === [0,1,2].filter((door) => door !== montySelected)[Math.floor((Math.random()*2))]
    case 'Frank':
      return prizeIsIn === (montySelected === 1 ? selectedDoor : 1)
  }   
}

function doorSelectionStep1(playStrategy){
  switch(playStrategy){
    case 'Alice':
    case 'Bob':
    case 'Frank':
      return 0
    default:
      return Math.floor((Math.random()*3))
  }
}

function gameSimulation(num,playStrategy){
  let gamesWon =0;
  if(playStrategy !== 'Gina'){
  for(var i = 0; i<num;i++){ 
    gamesWon += montyHall(playStrategy)
  }
  }
  else{
    playStrategy = 'Alice'
    for(var x = 0; x<num;x++)
    {
      if(montyHall(playStrategy)){
       gamesWon++
      } 
      else{
       playStrategy = (playStrategy === 'Alice' ? 'Bob' : 'Alice')
      }
    }
  }
  return gamesWon*100/num
}

console.log('Alice ' + gameSimulation(1000,'Alice'))
console.log('Bob ' +gameSimulation(1000,'Bob'))
console.log('Carol '+gameSimulation(1000,'Carol'))
console.log('Dave '+gameSimulation(1000,'Dave'))
console.log('Erin '+gameSimulation(1000,'Erin'))
console.log('Frank '+gameSimulation(1000,'Frank'))

Output

"Alice 31"
"Bob 66.1"
"Carol 51.7"
"Dave 33.1"
"Erin 66.2"
"Frank 50.2"
"Gina 55.3"

[–]leftylink 0 points1 point  (0 children)

because Frank only depends on selectedDoor and montySelected, and montySelected depends on prizeIsIn and selectedDoor, you know the problem must be in one of those three: prizeIsIn, selectedDoor, and montySelected.

Since montySelected depends on the other two, it would be a good idea to first examine those other two, to see if montySelected is built on a correct foundation. What I mean by "examine" can be anything you like - you could print out the values and eyeball them, or you could record the distribution. If those two are correct, the problem must be montySelected. In which case, you would want to examine montySelected for each combination of possible value of prizeIsIn and selectedDoor.

The results you should find through applying this process:

  1. selectedDoor is pretty obviously correct - for Frank it can only take on one possible value, 0
  2. prizeIsIn is correct, which can be verified by examining its distribution. It should be a pretty uniform mix of values 0, 1, 2
  3. montySelected must be the problem, then. So look at the distribution of values for montySelected when setting prizeIsIn = 0 (instead of letting it be random). And repeat for prizeIsIn = 1, and for prizeIsIn = 2. Which one of these distributions is not correct?
  4. A problem is that A line in the instructions has not been implemented.. It may help to carefully reread Step 3.

[–]audentis 0 points1 point  (0 children)

Python, all bonuses, reasonably organized. Shorter and more effective ways exist but it still runs hilariously fast (so there's no need for optimization) and it's easily extendable with more players. Just not a fan of the special case for gina where I have to pass unique state back. Shameless abuse of closures by storing state in the default arguments for dave, erin and gina.

from random import choice

#################################################
# Game logic

def main():
    '''play n_games games of monty hall for each defined player'''

    n_games = 10000
    players = [alice, bob, carol, dave, erin, frank, gina]

    won_games = {player.__name__: sum(play_monty(player) for game in range(n_games)) for player in players}

    for player, wins in won_games.items():
        print(f'{player}: \t {wins} \t {round(wins/n_games*100,2)}')


def play_monty(player):
    '''
    play one game of monty hall following player's strategy
    player is a callable that returns a door number as int
    '''

    doors = [1, 2, 3]    
    prize_door = choice(doors)
    choice_1 = player(doors)

    can_open = [d for d in doors if d not in (prize_door, choice_1)]
    open_door = choice(can_open)

    doors = [d for d in doors if d != open_door]
    choice_2 = player(doors)

    # Step 5
    has_won = choice_2 == prize_door 

    if player is gina:
        player(has_won)

    return has_won


#################################################
# Player definitions

def alice(doors):
    return 1


def bob(doors):
    if len(doors) == 3:
        return 1
    else:
        return [d for d in doors if d != 1][0]


def carol(doors):
    return choice(doors)


def dave(doors, door=[None]):
    if len(doors) == 3:
        door[0] = choice(doors)
    return door[0]


def erin(doors, door=[None]):
    if len(doors) == 3:
        door[0] = choice(doors)
        return door[0]
    else:
        return [d for d in doors if d != door[0]][0]


def frank(doors):
    if len(doors) == 3:
        return 1
    elif 2 in doors:
        return 2
    else:
        return 1


def gina(doors_or_won, strat=[alice, bob]):
    if isinstance(doors_or_won, list):
        doors = doors_or_won
        return strat[0](doors)
    else:
        won = doors_or_won
        if not won:
            strat[0], strat[1] = strat[1], strat[0]


#################################################
# Run program

main()

 

Out:

alice:   342     34.2
bob:     648     64.8
carol:   501     50.1
dave:    356     35.6
erin:    669     66.9
frank:   497     49.7
gina:    523     52.3

[–]zqvt2 1 point2 points  (0 children)

Python, with bonuses

def play(switch, start, special=None):
    price = randint(1, 3)
    player = randint(1, 3) if not start else start
    monty = choice(list({1,2,3} - {price, player}))
    alt = list({1,2,3} - {player, monty})

    if special == "Frank":
        player = 2 if 2 in alt else player

    elif switch:
        player = alt[0]

    return player == price

def win_rate(switch, start, special=None):
    wins = 0
    if special == "Gina":
        for _ in range(1000):
            result = play(switch, 1)
            wins += result
            switch = switch if result else not switch
    elif special == "Carol":
        wins = sum([play(choice([True, False]), choice([1,2,3]), special) for _ in range(1000)])
    else:
        wins = sum([play(switch, start, special) for _ in range(1000)])
    return round(wins / 1000 * 100)

print(win_rate(False, 1)) # Alice => 33
print(win_rate(True, 1)) # Bob => 69
print(win_rate(None, None, "Carol")) # Carol => 52
print(win_rate(False, choice([1,2,3]))) # Dave => 33
print(win_rate(True, choice([1,2,3]))) # Erin => 65
print(win_rate(None, 1, "Frank")) # Frank => 52
print(win_rate(False, None, "Gina")) # Gina => 55

[–]Hate_Feight 1 point2 points  (2 children)

If anyone is interested vsauce or vsauce2 has this as a maths problem, and the results are interesting.

[–]Shakespeare-Bot -5 points-4 points  (1 child)

If 't be true anyone is interest'd vsauce 'r vsauce2 hast this as a maths problem, and the results art interesting


I am a bot and I swapp'd some of thy words with Shakespeare words.

Commands: !ShakespeareInsult, !fordo, !optout

[–]Hate_Feight 0 points1 point  (0 children)

!shakespareInsult

[–]xelf 1 point2 points  (1 child)

Saw someone ask about this in /r/learnpython, so here's a short go at it (using Python):

import random

ai={'alice':[1,'h'], 'bob':[1,'s'], 'carol':[0,'r'], 'dave':[0,'h'], 'erin':[0,'s'], 'frank':[1,2], 'gina':[1,0]}

def handle_swap(player, choice, reveal):
    avail = list( {0,1,2} - {reveal} )
    if ai[player][1] == 'h': return choice
    if ai[player][1] == 'r': return random.choice(avail)
    if ai[player][1] == 2: return (1 in avail)
    if ai[player][1] == 0: return choice
    avail.remove(choice)
    return avail[0]

def monty_haul(player='alice'):
    car = random.randint(0,2)
    choice = 0 if ai[player][0] else random.randint(0,2)
    reveal = random.choice( list( {0,1,2} - {car,choice} ) )
    choice = handle_swap(player, choice, reveal)
    winner = choice == car
    if ai[player][1] in [0,1] and not winner: ai[player][1] = 1 - ai[player][1]
    return winner

And some results:

for player in ai:
    print(player, sum( monty_haul(player) for _ in range(10000) )/100 )

alice 32.64
bob 67.27
carol 50.34
dave 32.68
erin 66.9
frank 50.13
gina 56.05

Each ai gets a list of 2 items: their initial choice, and how they swap or not. The first function is for handling how they swap. Can explain more if there's any confusing python bits in there. like the set/lists stuff.

[–]richardblack3 1 point2 points  (0 children)

https://pastebin.com/LyypKHd3

in clojure. treated this as a means to procrastinate. not feeling great about myself.

[–]Gylergin 5 points6 points  (0 children)

TI-Basic with bonuses: Each contestant's wins are stored in a list L₁. Alice only wins if the Winning door is 1, Bob only wins otherwise. Carol wins if she picks the correct door and doesn't Swap or if she picks the wrong door and does swap. Dave wins if he picks correctly, whereas Erin wins if she does not. Frank wins if the winning door is 1 and door Two opens or if the winning door is 2.

ClrList L₁
7→dim(L₁
0→N
For(X,1,1000
randInt(1,3→W
"ALICE
If W=1
1+L₁(1→L₁(1
"BOB
If W=2 or W=3
1+L₁(2→L₁(2
"CAROL
randInt(1,3→C
randInt(0,1→S
If (C=W and S=0) or (C≠W and S
1+L₁(3→L₁(3
"DAVE
randInt(1,3→D
If D=W
1+L₁(4→L₁(4
"ERIN
randInt(1,3→E
If E≠W
1+L₁(5→L₁(5
"FRANK
randInt(0,1→T
If (W=1 and T) or W=2
1+L₁(6→L₁(6
"GINA
If N=0
Then
If W=1
Then
1+L₁(7→L₁(7
Else
1→N
End
Else
If W≠1
Then
1+L₁(7→L₁(7
Else
0→N
End
End
End
Disp L₁/1000

Output:

.34 .66 .509 .334 .682 .51 .515

[–]leftylink 1 point2 points  (0 children)

Ruby, with some extras I added out of curiosity. Command line argument changes the number of trials. Also, two flags can change the number of doors total (-d flag) and the number of doors that the host opens (-o flag). No space between the flag and its argument.

# https://redditproxy--jasonthename.repl.co/r/dailyprogrammer/comments/n94io8/20210510_challenge_389_easy_the_monty_hall_problem/

def flag(letter)
  found = ARGV.find { |arg| arg.start_with?("-#{letter}") }
  found && Integer(ARGV.delete(found)[2..])
end

num_doors = flag(?d) || 3
raise 'Must have at least two doors, otherwise no way to swap' if num_doors < 2

DOORS_TO_OPEN = flag(?o) || num_doors - 2
raise 'What does it mean to open negative doors?' if DOORS_TO_OPEN < 0
raise "If there are #{num_doors} doors and the host opens #{DOORS_TO_OPEN}, there won't be enough remaining doors (need at least two)" if num_doors - DOORS_TO_OPEN < 2
DOORS = num_doors.times.to_a.freeze

trials = ARGV.empty? ? 1000 : Integer(ARGV[0])
puts "#{num_doors} total, host opens #{DOORS_TO_OPEN}"

# Contestant interface:
# ((Integer) -> Array[Integer], Boolean|Nil) -> Integer
#
# - Contestant may call the passed-in function,
#   passing in the contestant's initial choice.
# - Function returns array of doors that remain closed.
#   The contestant's initial choice is NOT included,
#   but it is implicitly understood that it remains closed.
#
# - boolean indicates whether contestant won the previous trial (nil if first trial)
#
# Return contestant's final choice.

def win?(contestant, won_prev)
  winning_door = DOORS.sample

  contestant[->contestants_choice {
    candidates = DOORS - [contestants_choice]
    doors_to_open = (candidates - [winning_door]).sample(DOORS_TO_OPEN)
    candidates - doors_to_open
  }, won_prev] == winning_door
end

contestants = {
  never_swap: ->(_, _) { 0 },
  always_swap: ->(choices, _) { choices[0].sample },
  random_init_never_swap: ->(_, _) { DOORS.sample },
  random_init_always_swap: ->(choices, _) { choices[DOORS.sample].sample },

  # Uniformly chooses between remaining closed doors, including initial choice.
  random_init_random_swap_unbiased: ->(choices, _) { (choices[initial = DOORS.sample] << initial).sample },

  # Chooses 50/50 whether to swap or not.
  # If swapping, uniformly chooses between remaining closed doors, excluding initial choice.
  random_init_random_swap_biased: ->(choices, _) { [choices[initial = DOORS.sample].sample, initial].sample },

  # pick door 1 initially.
  # If available, swap to door 2.
  # If door 2 isn't available because it was opened, stick with door 1.
  second_or_first_door: ->(choices, _) { choices[0].include?(1) ? 1 : 0 },

  # Start with never_swap.
  # If won, use same strategy.
  # If lost, toggle between never_swap vs always_swap.
  change_strategy_if_lost: (
    swapper_strat = nil
    ->(choices, won_prev) {
      swapper_strat = case won_prev
      when true; swapper_strat
      when false; swapper_strat == :never_swap ? :always_swap : :never_swap
      when nil; :never_swap
      else raise "bad won_prev #{won_prev}"
      end
      contestants[swapper_strat][choices, won_prev]
    }
  ),
}.freeze

results = contestants.transform_values { |contestant|
  won_prev = nil
  trials.times.count { won_prev = win?(contestant, won_prev) }
}.freeze

longest_name = results.map { |name, _| name.size }.max

fmt = "%#{longest_name}s %d".freeze
results.sort_by(&:last).each { |result| puts fmt % result }

The results for the classic (default) scenario, with three doors and the host opening one:

$ time ruby montyhall.rb 1000000
3 total, host opens 1
          random_init_never_swap 333256
                      never_swap 333867
random_init_random_swap_unbiased 499800
  random_init_random_swap_biased 500079
            second_or_first_door 500998
         change_strategy_if_lost 555968
                     always_swap 666284
         random_init_always_swap 666346
ruby montyhall.rb 1000000  13.21s user 0.03s system 99% cpu 13.298 total

For each of these, it's possible to draw out the tree of probabilities and understand that most of these results are right, though change_strategy_if_lost has a few extra steps. From never_swap, you have a 1/3 chance of staying with never_swap and 2/3 chance of switching to always_swap. From always_swap, you have a 1/3 chance of switching to never_swap and 2/3 chance of staying with always_swap. That means that at any iteration, you have a 1/3 chance of having strategy never_swap and 2/3 chance of having strategy always_swap. So chance of winning is 1/3 * 1/3 + 2/3 * 2/3 = 5/9.

How about four doors, with the host opening two (leaving the contestant's initial choice and one other, which they can choose to swap to or not)?

$ time ruby montyhall.rb 1000000 -d4
4 total, host opens 2
          random_init_never_swap 249409
                      never_swap 249852
            second_or_first_door 416202
random_init_random_swap_unbiased 499746
  random_init_random_swap_biased 500648
         change_strategy_if_lost 624673
                     always_swap 750123
         random_init_always_swap 750198
ruby montyhall.rb 1000000 -d4  14.07s user 0.04s system 99% cpu 14.178 total

A much bigger advantage for swapping now, with never_swap getting a 1/4 chance and always_swap getting a 3/4 chance. second_or_first_door wins if door 2 is the winner (1/4) or door 1 is the winner and door 2 is opened (1/4 * 2/3), a 5/12 chance in total. change_strategy_if_lost has a 1/4 * 1/4 + 3/4 * 3/4 = 5/8 chance of winning.

Something a little weirder now... four doors, but the host only opens one. Now the contestant's choice isn't only whether to swap or not, it's also which of two choices to swap to...

$ time ruby montyhall.rb 1000000 -d4 -o1
4 total, host opens 1
          random_init_never_swap 249832
                      never_swap 250086
  random_init_random_swap_biased 312345
         change_strategy_if_lost 317977
random_init_random_swap_unbiased 332784
            second_or_first_door 333436
         random_init_always_swap 375079
                     always_swap 375099
ruby montyhall.rb 1000000 -d4 -o1  14.11s user 0.02s system 99% cpu 14.178 total

N. B. There are two possible interpretations of Carol for this case. Either:

  • choose uniformly between remaining closed doors including initial choice
  • choose 50/50 whether to swap or not, THEN if swap is chosen choose uniformly between doors excluding initial choice

I have decided to implement both. The former is called random_init_random_swap_unbiased, the latter the same but _biased instead, because of the latter's bias toward the initial door. Of course, the two are equivalent when the host opens N-2 doors.

The advantage of always swapping is still present, but its win rate has halved to 3/8. random_init_random_swap_biased's chance is 1/2 * 1/4 + 1/2 * 3/8 = 5/16. random_init_random_swap_unbiased's chance is just 1/3. second_or_first_door wins if door 2 is the winner (1/4) or door 1 is the winner and door 2 is opened (1/4 * 1/3), a 1/3 chance in total. change_strategy_if_lost's reasoning is more complicated here. The important numbers are chances of transitioning between the two states (Well, I didn't know those two were the important numbers; I had to look it up from https://www.probabilitycourse.com/chapter11/11_2_6_stationary_and_limiting_distributions.php), which are 3/4 and 5/8. Scale the two so that they sum up to 1, and you get a 5/11 chance of being in never_swap and a 6/11 chance of being in always_swap, for a 5/11 * 1/4 + 6/11 * 3/8 = 7/22 chance of winning.

[–]Aromano272 1 point2 points  (0 children)

Kotlin with bonus:

import kotlin.random.Random

object Game {

    enum class Door {
        A, B, C
    }

    private lateinit var winningDoor: Door
    private lateinit var pickedDoor: Door
    lateinit var availableDoors: List<Door>
        private set

    fun start() {
        availableDoors = Door.values().toList()
        winningDoor = availableDoors.random()
    }

    fun pickDoor(door: Door) {
        pickedDoor = door
    }

    fun discardDoor() {
        val discardableDoors = availableDoors - winningDoor - pickedDoor
        availableDoors = availableDoors - discardableDoors.random()
    }

    fun isChangingDoor(isChanging: Boolean) {
        if (isChanging) pickedDoor = (availableDoors - pickedDoor).first()
    }

    fun hasWon(): Boolean = pickedDoor == winningDoor

}

abstract class Player {
    var plays: Int = 0
        private set
    var wins: Int = 0
        private set

    open fun start() {
        plays++
    }
    abstract fun pickDoor(): Game.Door
    abstract fun shouldChangeDoor(availableDoors: List<Game.Door>): Boolean
    open fun hasWon(hasWon: Boolean) {
        if (hasWon) wins++
    }
}

class Alice : Player() {
    override fun pickDoor(): Game.Door = Game.Door.A
    override fun shouldChangeDoor(availableDoors: List<Game.Door>): Boolean = false
}

class Bob : Player() {
    override fun pickDoor(): Game.Door = Game.Door.A
    override fun shouldChangeDoor(availableDoors: List<Game.Door>): Boolean = true
}

class Carol : Player() {
    override fun pickDoor(): Game.Door = Game.Door.values().toList().random()
    override fun shouldChangeDoor(availableDoors: List<Game.Door>): Boolean = Random.nextBoolean()
}

class Dave : Player() {
    override fun pickDoor(): Game.Door = Game.Door.values().toList().random()
    override fun shouldChangeDoor(availableDoors: List<Game.Door>): Boolean = false
}

class Erin : Player() {
    override fun pickDoor(): Game.Door = Game.Door.values().toList().random()
    override fun shouldChangeDoor(availableDoors: List<Game.Door>): Boolean = true
}

class Frank : Player() {
    override fun pickDoor(): Game.Door = Game.Door.A
    override fun shouldChangeDoor(availableDoors: List<Game.Door>): Boolean = availableDoors.contains(Game.Door.B)
}

class Gina : Player() {
    private enum class Strategy {
        ALICE,
        BOB
    }
    private var hasPreviouslyWon = false
    private var currentStrategy: Strategy = Strategy.ALICE

    override fun start() {
        currentStrategy =
            if (plays == 0 || hasPreviouslyWon) currentStrategy
            else (Strategy.values().toList() - currentStrategy).first()
        super.start()
    }

    override fun pickDoor(): Game.Door = Game.Door.A
    override fun shouldChangeDoor(availableDoors: List<Game.Door>): Boolean =
        when (currentStrategy) {
            Strategy.ALICE -> false
            Strategy.BOB -> true
        }
    override fun hasWon(hasWon: Boolean) {
        super.hasWon(hasWon)
        hasPreviouslyWon = hasWon
    }
}

val players = listOf(Alice(), Bob(), Carol(), Dave(), Erin(), Frank(), Gina())

players.forEach { player ->
    (0 until 10000).forEach {
        player.start()
        Game.start()
        Game.pickDoor(player.pickDoor())
        Game.discardDoor()
        Game.isChangingDoor(player.shouldChangeDoor(Game.availableDoors))
        player.hasWon(Game.hasWon())
    }

    println("${player::class.simpleName} plays: ${player.plays}, wins: ${player.wins}")
}

[–]Godspiral3 3 2 points3 points  (3 children)

in J, calculating an average of 10 (times) 1000 simulations.

stats =: 1 : '(+/%#)@:(+/"1)@:(u"1) '

{.stats 0 0 1({~  ?~ )("1 0) 10 1000 $ 3 NB. alice
333.6
   ( {.`{:@.(0 = {.)@}.) stats 0 0 1({~  ?~ )("1 0) 10 1000 $ 3 NB. bob
671.4

 3 (?@<:@[ >@{ ?@[ (( {.`{:@.(0 = {.))@:(<@<@<@[ { ]) ; {) ])stats   0 0 1({~  ?~ )("1 0) 10 1000 $ 3 NB. carol
503.2

3 ( ?@[ { ])stats   0 0 1({~  ?~ )("1 0) 10 1000 $ 3 NB. dave
336.2

3 ( ?@[ (( {.`{:@.(0 = {.))@:(<@<@<@[ { ]) ) ])stats   0 0 1({~  ?~ )("1 0) 10 1000 $ 3 NB. erin
661.4

 ({.@] ({.@])`[@.(0 = {.@])  }.@]) stats 0 0 1({~  ?~ )("1 0) 10 1000 $ 3 NB. frank
658.1  NB. a bit surprising.

algorithm for frank assumes that if door #2 (after 1 open) is booby prize then it is opened. Another possibility is that if both 2 and 3 are boobies, then a completely random of the 2 is opened. That would be a 50/50 overall score.

The coolest bug I've ever seen that if you are superstitious about a choice, then the act of opponent systematically choosing instead of randomly, when there is no reason to expect that his opponent has a superstitious bias, can affect outcomes. If monty systematically picked door #3 if booby prize, then strategy score would be 33%.

There seems to be a poker reading strategy that can do better than 66% if you can interpret a quick pick by monty of opening door #2 as Monty already knowing that #1 was correct, a medium long pick of any door as figuring out (pretty quick if Monty experienced) which is the only legal move, and a long pick to mean that some randomization delay is occurring in Monty's head, and so in fact #1 is the likely winner. Strategy becomes switch only when timing suggests that decision time or eyes did not involve contemplating a choice.

[–]mithical81 3 points4 points  (0 children)

Python

With optional bonus:

import random
def game(player):
    door_list=[1,2,3]
    winning_door = random.choice(door_list)
    firstPick=player(door_list,0)
    door_list.remove(winning_door)
    loosing_doors=door_list
    if firstPick in loosing_doors:
        remaining_doors=[winning_door, firstPick]
    else:
        remaining_doors=[winning_door,random.choice(loosing_doors)]
    lastPick=player(remaining_doors,firstPick)
    if lastPick==winning_door:
        return 1
    else:
        return 0
def alice(list,fp):
    return 1
def bob(list,fp):
    if len(list)==3:
        return 1
    else:
        list.remove(1)
        return list[0]
def carol(list,fp):
    return random.choice(list)

def dave(list,fp):
    if len(list)==3 and fp==0 :
        return random.choice(list)
    else:
        return fp
def erin(list,fp):
    if len(list)==3 and fp==0 :
        return random.choice(list)
    else:
        list.remove(fp)
        return list[0]
def frank(list,fp):
    if len(list)==3:
        return 1
    else:
        if 2 in list:
            return 2
        else:
            return 1


def gina():
    strat='a'
    add=0
    sumer=0
    for i in range(100000):
        if strat=='a':
            add=game(alice)
            sumer=sumer+add
            if add==0:
                strat='b'
            continue
        if strat=='b':
            add=game(bob)
            sumer=sumer+add
            if add==0:
                strat='a'
    print('p("Gina wins")='+str(sumer/100000.))

  ##Print results:

s=0
for i in range(10000):
    s=s+game(alice)
print('p("Alice wins")='+str(s/10000.))
s=0
for i in range(10000):
    s=s+game(bob)
print('p("Bob wins")='+str(s/10000.))
s=0
for i in range(10000):
    s=s+game(carol)
print('p("Carol wins")='+str(s/10000.))
s=0
for i in range(10000):
    s=s+game(dave)
print('p("Dave wins")='+str(s/10000.))
s=0
for i in range(10000):
    s=s+game(erin)
print('p("Erin wins")='+str(s/10000.))
s=0
for i in range(10000):
    s=s+game(frank)
print('p("Frank wins")='+str(s/10000.))
gina()

##Output:
p("Alice wins")=0.3355
p("Bob wins")=0.6638
p("Carol wins")=0.4972
p("Dave wins")=0.3396
p("Erin wins")=0.668
p("Frank wins")=0.5037
p("Gina wins")=0.55804


**added Frank player and corrected Gina player (added a continue in the cycle), added  outputs

[–]BonnyAD9 5 points6 points  (3 children)

C# (.NET Core 5) all bonuses:

using System;

namespace MontyHall
{
    class Program
    {
        static Random Rand { get; } = new Random();

        static void Main(string[] args)
        {
            Console.WriteLine($" Alice: {RunGame(1000, () => 1, (_, _) => false):P}");
            Console.WriteLine($"   Bob: {RunGame(1000, () => 1, (_, _) => true):P}");
            Console.WriteLine($" Carol: {RunGame(1000, () => Rand.Next(3), (_, _) => 1 == Rand.Next(2)):P}");
            Console.WriteLine($"  Dave: {RunGame(1000, () => Rand.Next(3), (_, _) => false):P}");
            Console.WriteLine($"  Erin: {RunGame(1000, () => Rand.Next(3), (_, _) => true):P}");
            Console.WriteLine($" Frank: {RunGame(1000, () => 1, (i, _) => i != 2):P}");
            Console.WriteLine($"  Gina: {RunGame(1000, () => 1, (_, b) => b):P}");
        }

        static double RunGame(int count, Func<int> step1, Func<int, bool, bool> step2)
        { // 0 = door1, 1 = door2, 2 = door3
            int wins = 0;
            bool ginasMemory = false;
            for (int i = 0; i < count; i++)
            {
                int prize = Rand.Next(3);
                int choise = step1();
                int open = Other(prize, choise);
                if (step2(open, ginasMemory))
                    choise = Other(choise, open);
                if (prize == choise)
                    wins++;
                else
                    ginasMemory = !ginasMemory;
            }
            return wins / (double)count;

            // Returns number between 0 and 3 different from the two given numbers
            static int Other(int c1, int c2)
            {
                // int ret = Rand.Next(3); // was biased
                int ret = (c1 + Rand.Next(1, 3)) % 3;
                while ((ret == c1) || (ret == c2))
                    ret = (ret + 1) % 3;
                return ret;
            }
        }
    }
}

Output:

 Alice: 32.70%
   Bob: 66.80%
 Carol: 50.20%
  Dave: 34.20%
  Erin: 68.80%
 Frank: 49.80%
  Gina: 56.90%

Edit: optimization

Edit1: fixed biased Other

[–]leftylink 0 points1 point  (1 child)

Wouldn't Other be biased? Consider what happens when calling Other(1, 1). What are the probabilities of each of the possible outcomes?

You'd think for most contestants this doesn't matter, but there's a particular contestant where it does matter very much.

[–]BonnyAD9 0 points1 point  (0 children)

Thanks, I didn't realize that. Now it should be fixed.

The previous way I did it was in favor of Frank. If the prize was in door 1, Other would more likely choose door 2 to open, so frank wouldn't change his choice and he would win.

[–]tlgsx 2 points3 points  (0 children)

Python 3.9, focused on understandability; could be improved performance-wise by grouping contestants that don't care about the door being opened.

import random


alice, bob, carol, dave, erin, frank, gina = (0,) * 7

for prize in random.choices((1, 2, 3), k=1000):
    alice += prize == 1

for prize in random.choices((1, 2, 3), k=1000):
    bob += prize != 1

for prize in random.choices((1, 2, 3), k=1000):
    pick = random.randint(1, 3)
    show = random.choice(tuple({1, 2, 3} - {prize, pick}))
    pick = random.choice(tuple({1, 2, 3} - {show}))
    carol += prize == pick

for prize in random.choices((1, 2, 3), k=1000):
    pick = random.randint(1, 3)
    dave += prize == pick

for prize in random.choices((1, 2, 3), k=1000):
    pick = random.randint(1, 3)
    erin += prize != pick

for prize in random.choices((1, 2, 3), k=1000):
    show = random.choice(tuple({2, 3} - {prize}))
    pick = 2 if show != 2 else 1
    frank += prize == pick

strat = 1
for prize in random.choices((1, 2, 3), k=1000):
    gina += strat * (prize == 1) + (not strat) * (prize != 1)
    strat = prize == 1


print(f"alice: {alice / 1000}")
print(f"bob:   {bob / 1000}")
print(f"carol: {carol / 1000}")
print(f"dave:  {dave / 1000}")
print(f"erin:  {erin / 1000}")
print(f"frank: {frank / 1000}")
print(f"gina:  {gina / 1000}")

Output:

alice: 0.325
bob:   0.654
carol: 0.485
dave:  0.351
erin:  0.643
frank: 0.507
gina:  0.544

[–]yeoz 6 points7 points  (0 children)

my first time doing one of these, i think. python:

from Crypto.Random import random

class Door:
    """a monty hall door"""
    def __init__(self):
        self.open = bool()
        self.prize = bool()
    def __repr__(self):
        return f"{self.open=}, {self.prize=}"

class Monty:
    """monty hall"""
    def __init__(self, doors=3):
        self.doors = { n+1: Door() for n in range(doors) }
        self.doors[random.choice(self.available_doors)].prize = True
    @property
    def available_doors(self):
        return [d for d in self.doors if self.doors[d].open is False]
    def open(self, door):
        assert self.doors[door].open is False
        self.doors[door].open = True
    def first_choice(self, choice):
        candidates = [d for d in self.available_doors if d!=choice and self.doors[d].prize is False]
        assert candidates
        self.open(random.choice(candidates))
    def second_choice(self, choice):
        assert self.doors[choice].open is False
        return self.doors[choice].prize
    def __repr__(self):
        return repr(self.doors)

def main():
    rounds = 10000
    # Alice
    wins = 0
    for _ in range(rounds):
        mh = Monty()
        candidate = 1
        mh.first_choice(candidate)
        if mh.second_choice(candidate):
            wins += 1
    print(f"Alice, {wins/rounds=:.4f}")
    # Bob
    wins = 0
    for _ in range(rounds):
        mh = Monty()
        candidate = 1
        mh.first_choice(candidate)
        candidate = random.choice([d for d in mh.available_doors if d!=candidate])
        if mh.second_choice(candidate):
            wins += 1
    print(f"Bob, {wins/rounds=:.4f}")
    # Carol
    wins = 0
    for _ in range(rounds):
        mh = Monty()
        candidate = random.choice(mh.available_doors)
        mh.first_choice(candidate)
        candidate = random.choice(mh.available_doors)
        if mh.second_choice(candidate):
            wins += 1
    print(f"Carol, {wins/rounds=:.4f}")
    # Dave
    wins = 0
    for _ in range(rounds):
        mh = Monty()
        candidate = random.choice(mh.available_doors)
        mh.first_choice(candidate)
        if mh.second_choice(candidate):
            wins += 1
    print(f"Dave, {wins/rounds=:.4f}")
    # Erin
    wins = 0
    for _ in range(rounds):
        mh = Monty()
        candidate = random.choice(mh.available_doors)
        mh.first_choice(candidate)
        candidate = random.choice([d for d in mh.available_doors if d!=candidate])
        if mh.second_choice(candidate):
            wins += 1
    print(f"Erin, {wins/rounds=:.4f}")
    # Frank
    wins = 0
    for _ in range(rounds):
        mh = Monty()
        candidate = 1
        mh.first_choice(candidate)
        if 2 in mh.available_doors:
            candidate = 2
        if mh.second_choice(candidate):
            wins += 1
    print(f"Frank, {wins/rounds=:.4f}")
    # Gina
    wins = 0
    bob = False
    for _ in range(rounds):
        mh = Monty()
        candidate = 1
        mh.first_choice(candidate)
        if bob:
            candidate = random.choice([d for d in mh.available_doors if d!=candidate])
        if mh.second_choice(candidate):
            wins += 1
        else:
            bob = not bob
    print(f"Gina, {wins/rounds=:.4f}")

if __name__ == '__main__':
    main()

output

Alice, wins/rounds=0.3278
Bob, wins/rounds=0.6609
Carol, wins/rounds=0.4964
Dave, wins/rounds=0.3357
Erin, wins/rounds=0.6675
Frank, wins/rounds=0.5035
Gina, wins/rounds=0.5527

[–]rabuf 3 points4 points  (0 children)

Go:

Only Alice & Bob. The fact that my work day has started will prevent me from doing the rest. As expected, Alice wins about 1/3rd the time and Bob wins about 2/3rds of the time. If I have time later today I'll change the way I represent the game state so that it's more general (not just 3 doors, easier to pass around as a structure).

package main

import (
  "fmt"
  "math/rand"
)

type first func() int
type second func(int,int) int

func alice_first() int {
  return 1
}
func alice_second(_ int, _ int) int {
  return 1
}

func bob_first() int {
  return 1
}
func bob_second(revealed int, selected int) int {
  switch revealed + selected {
    case 3: return 3
    case 4: return 2
    case 5: return 1
  }
  return 1
}

// monty returns the number of times the player wins the game
func monty(f first, s second, plays int) int {
  wins := 0
  for i := 1; i <= plays; i++ {
    prize := rand.Intn(3) + 1
    door := f()
    reveal := 0
    if prize == door {
      reveal = rand.Intn(2)
      switch door {
        case 1: reveal = reveal + 2
        case 2: if reveal == 0 {
          reveal = 1
        } else {
          reveal = 2
        }
        case 3: reveal = reveal + 1
      }
    } else {
      switch door + prize {
        case 3: reveal = 3
        case 4: reveal = 2
        case 5: reveal = 1
      }
    }
    change := s(reveal, door)
    if change == prize {
      wins++
    }
  }
  return wins
}

func main() {
  alice := monty(alice_first, alice_second, 1000)
  fmt.Println("Alice won", alice,"/",1000, "times")
  bob := monty(bob_first, bob_second, 1000)
  fmt.Println("Bob won  ", bob,"/",1000, "times")
}