3
\$\begingroup\$

I created a visualization to Monty Hall with python. this is my first program with python tkinter. I'm new to python (and OOP in python). I used three pictures for the doors. Adding them here.

download the doors gifs - https://ufile.io/2h7fb

Or, download separately:

door1.gif - https://ibb.co/jSNntw

door2.gif - https://ibb.co/bTCntw

door3.gif - https://ibb.co/bs0U6G

Would love to get some review. I'm sure my object oriented skills are not good at the moment.

from tkinter import Tk, Canvas, Button, PhotoImage
import random


DOOR_1 = 1
DOOR_2 = 2
DOOR_3 = 3
DOORS = (DOOR_1, DOOR_2, DOOR_3)


class MontyHall(Tk):
    def __init__(self):
        Tk.__init__(self)
        self.total_counter = 0
        self.winning_switchers = 0
        self.winning_non_switchers = 0
        self.geometry("600x600")
        self.title("Monty Hall")
        self.rowconfigure(2, weight=1)
        self.canvas = Canvas(self, width=600, height=500)
        self.text = self.canvas.create_text(300, 120)
        self.answer = self.canvas.create_text(300, 140)
        self.text_switching = self.canvas.create_text(300, 30)
        self.text_not_switching = self.canvas.create_text(300, 60)
        self.door1_pic = PhotoImage(file='door1.gif')
        self.door2_pic = PhotoImage(file='door2.gif')
        self.door3_pic = PhotoImage(file='door3.gif')
        self.door1_show = self.canvas.create_image(100, 350, image=self.door1_pic)
        self.door2_show = self.canvas.create_image(300, 350, image=self.door2_pic)
        self.door3_show = self.canvas.create_image(500, 350, image=self.door3_pic)
        self.canvas.grid(row=2, sticky='s')
        self.canvas.tag_bind(self.door1_show, "<Button-1>", self.door1)
        self.canvas.tag_bind(self.door2_show, "<Button-1>", self.door2)
        self.canvas.tag_bind(self.door3_show, "<Button-1>", self.door3)

    def door1(self, *args):
        self.canvas.itemconfig(self.text, text="you chose door number 1")
        self.choose_door(DOOR_1)

    def door2(self, *args):
        self.canvas.itemconfig(self.text, text="you chose door number 2")
        self.choose_door(DOOR_2)

    def door3(self, *args):
        self.canvas.itemconfig(self.text, text="you chose door number 3")
        self.choose_door(DOOR_3)

    def choose_door(self, door_chosen):
        self.block_doors(4)
        self.canvas.itemconfig(self.answer, text="")
        correct_door = random.choice(DOORS)
        if correct_door != door_chosen:
            close = random.choice(list(set(DOORS) - {correct_door, door_chosen}))
        else:
            close = random.choice(list(set(DOORS) - {door_chosen}))
        self.block_doors(close)
        self.options(door_chosen, correct_door, close)

    def block_doors(self, block_door_number):
        if block_door_number == 4:
            self.canvas.tag_unbind(self.door1_show, "<Button-1>")
            self.canvas.tag_unbind(self.door2_show, "<Button-1>")
            self.canvas.tag_unbind(self.door3_show, "<Button-1>")
        if block_door_number == 3:
            self.canvas.delete(self.door3_show)
        elif block_door_number == 2:
            self.canvas.delete(self.door2_show)
        elif block_door_number == 1:
            self.canvas.delete(self.door1_show)

    def unhide_door(self, open_door):
        if open_door == 1:
            self.canvas.tag_bind(self.door2_show, "<Button-1>", self.door2)
            self.canvas.tag_bind(self.door3_show, "<Button-1>", self.door3)
            self.door1_show = self.canvas.create_image(100, 350, image=self.door1_pic)
            self.canvas.tag_bind(self.door1_show, "<Button-1>", self.door1)
        if open_door == 2:
            self.canvas.tag_bind(self.door1_show, "<Button-1>", self.door1)
            self.canvas.tag_bind(self.door3_show, "<Button-1>", self.door3)
            self.door2_show = self.canvas.create_image(300, 350, image=self.door2_pic)
            self.canvas.tag_bind(self.door2_show, "<Button-1>", self.door2)
        if open_door == 3:
            self.canvas.tag_bind(self.door1_show, "<Button-1>", self.door1)
            self.canvas.tag_bind(self.door2_show, "<Button-1>", self.door2)
            self.door3_show = self.canvas.create_image(500, 350, image=self.door3_pic)
            self.canvas.tag_bind(self.door3_show, "<Button-1>", self.door3)

    def options(self, door_number, correct_door, closed_door):
        self.total_counter += 1

        def show_results():
            self.canvas.itemconfig(self.text_switching,
                                   text='Switching won {0:5} times out of {1} ({2:.2f}% of the time)'
                                   .format(self.winning_switchers, self.total_counter,
                                           (self.winning_switchers / self.total_counter * 100)))
            self.canvas.itemconfig(self.text_not_switching,
                                   text='Not switching won {0:5} times out of {1} ({2:.2f}% of the time)'.
                                   format(self.winning_non_switchers, self.total_counter,
                                          (self.winning_non_switchers / self.total_counter * 100)))

        def change(*args):
            button1.grid_forget()
            button2.grid_forget()
            chosen_door = next(iter(set(DOORS) - {door_number, closed_door}))
            if chosen_door == correct_door:
                self.canvas.itemconfig(self.answer, text="you were right")
                self.winning_switchers += 1
            else:
                self.canvas.itemconfig(self.answer, text="you were wrong")
            self.unhide_door(closed_door)

        def keep(*args):
            button1.grid_forget()
            button2.grid_forget()
            if door_number == correct_door:
                self.canvas.itemconfig(self.answer, text="you were right")
                self.winning_non_switchers += 1
            else:
                self.canvas.itemconfig(self.answer, text="you were wrong")
            self.unhide_door(closed_door)

        button1 = Button(self, text='Click to change door')
        button1.bind("<Button-1>", change)
        button1.grid(row=0, sticky='w')

        button2 = Button(self, text='Click to keep door')
        button2.bind("<Button-1>", keep)
        button2.grid(row=1, sticky='w')
        show_results()

if __name__ == "__main__":
    application = MontyHall()
    application.mainloop()
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

Overview

You've done an excellent job:

  • Overall code layout is good
  • You did a good job partitioning code into functions
  • You leveraged code written by others with the imports
  • Used meaningful names for classes, functions and variables

Here are some adjustments for you to consider, mainly for coding style.

Documentation

Add comments at the top of the file to state the purpose of the code. For example:

'''
Monty Hall problem.
Here's how it works... add summary here
'''

Instructions

When I run the code, all I see is three doors. I don't know what I am expected to do. You should display some simple instructions in the GUI to prompt the user for expected actions, such as:

Click on one of the doors to blah, blah, blah...

Efficiency

In the unhide_door function, it is wasteful to check the value of the open_door variable in three separate if statements. They can be combined into a single if/elif:

    if open_door == 1:
        // ...
    elif open_door == 2:
        // ...
    elif open_door == 3:

You partially did this in function block_doors. However, you should merge the two if statements into one if/elif.

Simpler

Consider removing these constants; it might be simpler just to use the numbers themselves:

DOOR_1 = 1
DOOR_2 = 2
DOOR_3 = 3

DRY

There are a lot of instances where the the code is repeated 3 times, once for each door. Although I don't have a specific recommendation in mind, it would be worth spending some time looking for an opportunity to combine common code.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.