CS21 Lab 4: Functions and While Loops

Written part Due Friday, February 23, at the start of class

Due Saturday, February 24, by 11:59pm

Goals

The goals for this lab assignment are:

  • Develop your understanding of the stack and how function calls and returns work.

  • Write programs with multiple functions.

  • Solve problems using indefinite while loops.

  • Learn to use the random library to make pseudo-random choices.

Getting and Working on Lab04 Code

Make sure you are working in the correct directory when you edit and run lab 04 files.

Run update21 to get the starting point code of the next lab assignment. This will create a new ~/cs21/labs/04 directory for your lab work. Edit and run Lab 04 programs from within your cs21/labs/04 directory.

Make sure all programs are saved to your cs21/labs/04 directory! Files outside that directory will not be graded.

$ update21
$ cd ~/cs21/labs/04
$ pwd
/home/username/cs21/labs/04
$ ls
(should see your program files here)

Then edit and run program files in your ~/cs21/labs/04 directory:

$ code filename.py
$ python3 filename.py

Writing with functions

For our first lab with multiple functions in a single file, please look at the following programming tips.

As you write programs, use good programming practices:

  • Use a comment at the top of the file to describe the purpose of the program (see example).

  • All programs should have a main() function (see example).

  • Use variable names that describe the contents of the variables.

  • Write your programs incrementally and test them as you go. This is really crucial to success: don’t write lots of code and then test it all at once! Write a little code, make sure it works, then add some more and test it again.

  • Don’t assume that if your program passes the sample tests we provide that it is completely correct. Come up with your own test cases and verify that the program is producing the right output on them.

  • Avoid writing any lines of code that exceed 80 columns.

    • Always work in a terminal window that is 80 characters wide (resize it to be this wide)

    • In vscode, at the bottom right in the window, there is an indication of both the line and the column of the cursor.

Function Comments

All functions should have a top-level comment! Please see our function example page if you are confused about writing function comments.

1. Written Assignment: Stack Diagram

The first part of this lab is a written assignment to trace through some Python code, show the program output and draw the stack. Download the following .pdf file, print it out: lab4stack.pdf

You should write your solution on the print out, and submit it at the start of the class on Friday. Please submit a printed hard copy of your solution in class. Do not email us pdfs, screenshots, or other electronic versions of your solution.

2. Let’s Make a Deal

In this lab, you will simulate a game from the TV show "Let’s Make a Deal". The game is often called the "Monty Hall" problem, after a long time host of the show. The game is played as follows:

  1. There are three closed doors, behind one of which is a grand prize (typically described as a car), and behind the other two are lesser prizes (typically described as goats). The door containing the grand prize is chosen at random by the host.

  2. The contestant chooses one of the doors, but the door is not opened yet.

  3. The host then opens one of the other two doors, revealing one of the lesser prizes. The host knows which door contains the grand prize, and always opens a door with a lesser behind it.

  4. The contestant is then given the option to switch their choice to the other unopened door.

  5. If the contestant chooses not to switch, they win whatever prize is behind the door they originally chose. If they choose to switch, they win whatever is behind the other unopened door.

Initially the contestant has a 1 in 3 chance of winning the grand prize. Does the probability of winning change if the contestant switches doors? You will write a program to simulate this game and answer this question. Your solution will contain two versions of the game. In the interactive version the user is prompted to choose a door and then asked if they want to switch. In the simulated version the user’s choice is made randomly and the program will use an always switch or never switch strategy.

In this lab, we will describe the functions you will need to write to implement the game. You will need to implement the functions exactly as described with the same input parameters, and return types. In later labs, you will have more flexibility in the design of your functions.

3. Getting Started

As a first step, you will write some small helper functions to get valid input from the user. Write and test each function by making some sample test calls in main() before moving on the next function.

3.1. get_player_choice()

Start by writing the get_player_choice() function using the prototype below. This function will prompt the user to choose a door and return the user’s choice. If the user enters an invalid choice, print an error message and prompt the user again until a valid choice is made.

def get_player_choice():
    """
    Prompts the user to choose a door and returns the user's choice.
    re-prompts the user until a valid choice is made.

    returns: (str) choice of "1", "2", or "3"
    """

Once your function is complete, uncomment the first two lines in main() to call your get_player_choice() function and print the result. Make sure your function returns a string.

def main():
    user_choice = get_player_choice()
    print("Player chose door #%s" % (user_choice))

Some sample runs from the main() function are shown below.

$ python3 deal.py

Which door do you choose? 1, 2, or 3?: 1
Player picked door #1
$ python3 deal.py

Which door do you choose? 1, 2, or 3?: a
please type a valid response
Which door do you choose? 1, 2, or 3?: test
please type a valid response
Which door do you choose? 1, 2, or 3?: -5
please type a valid response
Which door do you choose? 1, 2, or 3?: 8
please type a valid response
Which door do you choose? 1, 2, or 3?: 12
please type a valid response
Which door do you choose? 1, 2, or 3?: 2
Player picked door #2

3.2. get_yes_or_no(prompt)

Next, write the get_yes_or_no(prompt) function using the prototype below.

def get_yes_or_no(prompt):
    """
    Prompts the user to enter "y" or "n" and returns the user's choice.
    Re-prompts the user until a valid choice is made.

    prompt: (str) prompt to display to the user

    returns: (str) choice of "y" or "n"
    """

Prompt the user with the string prompt which should be a question that can be answered with "y" or "n". This function should only allow return values of "y" or "n". If the user enters an invalid choice, print an error message and prompt the user repeatedly until they enter a valid choice.

Note that in main(), we call get_yes_or_no() with the prompt "Do you want to play the game interactively (y/n)?". This text prompt is copied to the parameter prompt in the function get_yes_or_no(prompt). You should not type the string "Do you want to play the game interactively (y/n)?" in the get_yes_or_no(prompt) function. Later, we will call get_yes_or_no(prompt) with other prompts. Uncomment the two lines in main() that call the get_yes_or_no(prompt) function and prints the result. Run your program to test your function. You may comment out your get_player_choice() function call in main() for now

$ python3 deal.py
Do you want to play the game interactively (y/n)? y
You chose: y
$ python3 deal.py
Do you want to play the game interactively (y/n)? n
You chose: n
$ python3 deal.py
Do you want to play the game interactively (y/n)? yes
Please enter only y or n
Do you want to play the game interactively (y/n)? maybe
Please enter only y or n
Do you want to play the game interactively (y/n)? puppies!
Please enter only y or n
Do you want to play the game interactively (y/n)? n
You chose: n

3.3. get_positive_int(prompt)

The function get_positive_int(prompt) with the prototype below is similar to the get_yes_or_no(prompt) function. This function will prompt the user to enter a positive integer and return the user’s choice. If the user enters an invalid choice, print an error message and prompt the user repeatedly until they enter a valid choice. You may assume the user enters an integer value. You only have to verify that the integer is positive. Return the valid result as an integer type.

def get_positive_int(prompt):
    """
    Prompts the user to enter a positive integer and returns
    the user's choice. Re-prompts the user until a valid choice is made.

    prompt: (str) prompt to display to the user

    returns: (int) A valid non-negative integer entered by the user
    """

Uncomment the test code in main() to test your get_positive_int(prompt) function.

n = get_positive_int("How many rounds do you want to play? ")
print("You chose: %d" % (n))
$ python3 deal.py
How many rounds do you want to play? 2
You chose: 2
$ python3 deal.py
How many rounds do you want to play? 0
Please enter a valid number
How many rounds do you want to play? -2
Please enter a valid number
How many rounds do you want to play? 3
You chose: 3

3.4. Checkpoint

Before continuing, ensure that all your functions to get user input are working correctly. We will next transition to building the interactive game.

For now, you can comment out the calls to get_positive_int(prompt) and get_yes_or_no(prompt) in main(). Additionally, you can remove the call to get_player_choice() in main().

4. Building the Interactive Game

Once your input functions are working, you can build the interactive game. The interactive game is played as follows:

  1. The game picks a random door to hide the grand prize. The door ID is stored as the string "1", "2", or "3".

  2. The player is prompted to choose a door.

  3. The game picks a door to open, which is not the door the player chose and not the door with the grand prize.

  4. The player is prompted to decide if they want to switch doors.

  5. If the player chooses to switch, the game will swap the player’s original choice with the remaining unopened door.

  6. The game will then reveal the prize behind the player’s final choice.

  7. The game will return a Boolean value indicating if the player won the grand prize.

Part of game is implemented in the interactive_game() function. Make a call to interactive_game() in main() to test the current prototype of the function.

  1. The choice("123") chooses a random character "1", "2", or "3" for the location of the grand prize.

  2. The interactive_game() function calls get_player_choice() to get the player’s choice.

To complete the interactive_game() function, you will need to implement the following functions:

  1. reveal_door(guess, secret)

  2. swap_guess(guess, revealed)

4.1. reveal_door(guess, secret)

The reveal_door(guess, secret) function is called with two single character strings: the player’s choice guess and the location of the grand prize secret. Both of these parameters have the value "1", "2", or "3". The reveal_door function should pick and return a door that is neither the value of guess nor secret. See the prototype below.

def reveal_door(guess, secret):
    """
	Returns a door that is not the player's choice or the grand prize.

	guess: (str) the player's choice of "1", "2", or "3"
	secret: (str) the location of the grand prize "1", "2", or "3"

	returns: (str) the door that is revealed
	"""

The logic of how to do this is a bit tricky, but here is concise way to solve it:

  1. Initialize an empty string accumulator doors to the empty string "". This variable will store the possible doors to reveal. There will always be at least one possible door, and in some cases when the user guess and secret are the same, two door choices.

  2. Loop through all characters the string "123". If the character ch is not equal to guess and ch is not equal to secret, add the ch to doors.

  3. Return a random character from the string doors, using the choice(doors). The choice function on a string will return a random character from the string.

You may want to test your reveal_door(guess, secret) function by making a few calls to it with different values of guess and secret in interactive_game(). Try saving and printing the return value of reveal_door(guess, secret) to see if it is working correctly.

Table 1. Sample return values of reveal_door
call possible return values

reveal_door("1", "2")

"3"

reveal_door("2", "1")

"3"

reveal_door("1", "1")

"2" or "3"

reveal_door("2", "3")

"1"

4.2. swap_guess(guess, revealed)

After implementing and testing reveal_door, implemenent the swap_guess(guess, revealed) function.

def swap_guess(guess, reveal):
    """
	Returns the door that is not the player's guess or the recently
	revealed door.

	guess: (str) the player's choice of "1", "2", or "3"
	reveal: (str) the location of the revealed door "1", "2", or "3"

    This function assumes guess and reveal are different,
	so there is only one possible door to return.

	returns: (str) The third door that is neither the player's guess
	         nor the revealed door
	"""

This function is called with two single character strings: the player’s choice guess and the door that was revealed revealed. Both of these parameters have the value "1", "2", or "3". The swap_guess function should return the door that was not the original guess and not the revealed door. Note in this case, guess and revealed are different. Therefore, there can only be one possible door to return.

Again, the logic of how to do this can be a bit tricky, but here is a concise way to solve it:

  1. Loop through all characters the string "123".

  2. If the character ch is not equal to guess and ch is not equal to revealed, return ch. This is the second door that the player can choose.

You may want to test your swap_guess(guess, revealed) function by making a few calls to it with different values of guess and revealed in interactive_game(). Try saving and printing the return value of swap_guess(guess, revealed) to see if it is working correctly.

Table 2. Sample return values of swap_guess
call possible return values

swap_guess("1", "2")

"3"

swap_guess("2", "1")

"3"

swap_guess("1", "3")

"2"

swap_guess("2", "3")

"1"

4.3. Completing the Interactive Game

reveal_door(guess, secret) and swap_guess(guess, revealed) are the only additional functions you need to complete the interactive game. Follow the outline in Section Section 4 to complete the implementation of the interactive_game() function. You can remove the print that shows the original player’s choice. Sample output from one run of the game is shown below.

$ python3 deal.py
Which door do you choose? 1, 2, or 3?: 1
The prize is not behind door 2
Would you like to swap (y/n)? y
Swapping to door #3
You won the grand prize!
$ python3 deal.py
Which door do you choose? 1, 2, or 3?: 2
The prize is not behind door 3
Would you like to swap (y/n)? n
Sticking with door #2
Better luck next time
The grand prize was behind door number 1

5. Playing multiple games

At this point, you should be able to play a single round of the interactive game. Once you are able to play a single round, modify your main() function to play multiple rounds of the game and track the number of games you win.

Use the accumulator pattern to play multiple rounds of the interactive game and count the total number of games you win. Note that a correct implementation of interactive_game will return a Boolean value indicating if the player won the grand prize. You can use this value to count the number of games won.

Test your solution. The main function should now display the total number of wins and winning percentage.

$ python3 deal.py
How many rounds do you want to play? 3

Which door do you choose? 1, 2, or 3?: 1
The prize is not behind door 2
Would you like to swap (y/n)? n
Sticking with door #1
Better luck next time
The grand prize was behind door number 3

Which door do you choose? 1, 2, or 3?: 1
The prize is not behind door 2
Would you like to swap (y/n)? n
Sticking with door #1
Better luck next time
The grand prize was behind door number 3

Which door do you choose? 1, 2, or 3?: 1
The prize is not behind door 2
Would you like to swap (y/n)? n
Sticking with door #1
You won the grand prize!

You won 1 out of 3 games 33.33%

6. Simulating games

Playing several rounds of the game interactively can become tiring. As a final step, you will write one more function to simulate a game where the player’s initial choice is also made randomly. The function simulate_game(do_swap) has one Boolean parameter do_swap. If do_swap is True, the simulated game should automatically swap the guess after the door is revealed. If do_swap is False, the simulated game should not swap the guess.

def simulate_game(do_swap):
   """
   Simulates a single round of the game.

   do_swap: (bool) if True, the simulation will swap the initial guess
   after the door is revealed. Otherwise, the simulation will keep
   the original guess.

   Note: This function does not print anything or prompt the user.

   returns: (bool) True if the simulation won the grand prize, False otherwise
   """

This function is very similar to interactive_game, but the player’s choice is made randomly and there are no prompts or prints.

Reusing the choice function twice (once for the secret, and once for the player’s random guess), reveal_door, and swap_guess, write the simulate_game(do_swap) function to simulate a single round of the game. The function should return a Boolean value indicating if the player won the grand prize.

Aside from returning a value, this function has no additional output and it does not print anything.

This function is very similar to interactive_game, but the player’s choice is made randomly and there are no prompts or prints. When developing your function, you may want to add print statements to help you debug your code. However, when you are finished, you should remove these print statements.

6.1. Playing multiple simulated games

Finally, add support of playing multiple simulated games in your main function. First, use the get_yes_or_no function to ask the user if they want to play the game interactively. If the answer is "n", then ask the user if they always want to swap the guess. You can use the get_yes_or_no function to get this user response using a different prompt.

Inside your accumulator loop, call either interactive_game or simulate_game, depending on the user’s earlier responses. For simulated games, remember to pass a Boolean value for the swap strategy depending on the user’s response outside of the loop.

You should only need to make a few small modificatios in the main accumulator loop to support simulated games.

Test your solution for simulated games. The main function should now display the total number of wins and winning percentage for either interactive or simulated games.

Do you want to play the game interactively (y/n)? n
How many rounds do you want to play? 3
Always swap (y/n)? y
You won 3 out of 3 games 100.00%

Note that you should only prompt for the always swap strategy if the user does not want to play the game interactively. After answering the strategy question, the program should play the simulated games and display the results with no additional prompts or output except for the final win count and percentage.

Try simulating a large number of games. Is there a difference between the never swap and always swap strategies?

Answer the Questionnaire

After each lab, please complete the short Google Forms questionnaire. Please select the right lab number (Lab 04) from the dropdown menu on the first question.

Once you’re done with that, you should run handin21 again.

Submitting lab assignments

Remember to run handin21 to turn in your lab files! You may run handin21 as many times as you want. Each time it will turn in any new work. We recommend running handin21 after you complete each program or after you complete significant work on any one program.

Logging out

When you’re done working in the lab, you should log out of the computer you’re using.

First quit any applications you are running, including your vscode editor, the browser and the terminal. Then click on the logout icon (logout icon or other logout icon) and choose "log out".

If you plan to leave the lab for just a few minutes, you do not need to log out. It is, however, a good idea to lock your machine while you are gone. You can lock your screen by clicking on the lock xlock icon. PLEASE do not leave a session locked for a long period of time. Power may go out, someone might reboot the machine, etc. You don’t want to lose any work!