CS21 Lab 4: functions and while loops

Due Sunday, February 23, before midnight

Goals

  • 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.

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 include a comment explaining their purpose, parameters, return value, and describe any side effects. 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.

1.1. Requirements

Given the pdf handbout above:

  1. Draw the correct stack diagram.

  2. Show the output of the program.

2. Big picture for solving this lab

In this lab, you’ll build a two-player game in which the two players roll a six-sided die (simulated by having the program generate a random number) as they attempt to get as close as possible to a target value without going over it.

On each player’s turn, they decide whether or not to roll the die. If they roll, then the value is added to their score; if they exceed the target value, then the opponent wins. After the second player rolls, if both players have achieved the target score, then the game is a draw.

Here’s a sample game with a target value of 10 (changes to score are shown in bold):

Whose turn?

Roll?

Rolled value

Player 1 score

Player 2 score

Player 1

yes

4

4

0

Player 2

yes

2

4

2

Player 1

yes

3

7

2

Player 2

yes

5

7

7

Player 1

yes

4

11

8

At this point, Player 1 now has 11 points, which exceeds the target, so Player 2 wins.

Here’s another sample game, this time with a target value of 12:

Whose turn?

Roll?

Rolled value

Player 1 score

Player 2 score

Player 1

yes

5

5

0

Player 2

yes

6

5

6

Player 1

yes

4

9

6

Player 2

yes

1

9

7

Player 1

no

-

9

7

Player 2

yes

5

9

12

Player 1

yes

3

12

12

Player 2

no

-

12

12

At this point, after Player 2’s turn, both players' scores match the target (12), so the game is a draw.

Building a game like this requires planning and design. You shouldn’t just sit down in front of your computer and start writing code. In this lab, we’re going to guide you through writing each step of the process, building the program incrementally. In future labs, we’ll give you more opportunities to design and implement programs on your own.

Each section will have you write one function which will be part of the larger program. After you write each new function, we’ll have you test that function by calling it in main. Until you get almost to the very end, you’ll be modifying the main function in order to test each function. There is no need to save your main function from a last step as you work on each next step. At the very end, you’ll put all the functions together into a working version of the game.

All of your code for the rest of this lab should be in the file dice.py.

Before we dive in to the details, take a look at what the final running program looks like. This example uses the same numbers as above.

3. Prompt the user to enter 'y' or 'n'

The first function that we write will be used for asking the player if they want to roll the die, and then if they want to play again at the end of the game. However, the function needs to make sure that the user only answers "y" or "n" to these questions.

The get_yes_or_no(prompt) function that is provided in dice.py passes the prompt to the input function so that it asks the user a question, and then returns whatever they answer.

Modify the function so that it shows "Please enter only y or n" if they answer anything other than "y" or "n", and keeps showing that until they give a valid answer, which the function should then return.

3.1. Test the get_yes_or_no function in main

The main function that is provided in dice.py calls get_yes_or_no with the argument "Would you like to roll (y/n)?"

Run your program to test your get_yes_or_no function. You should not need to modify main for this, and should see similar outputs as below (user input in bold):

$ python3 dice.py
Would you like to roll (y/n)? y

You chose: y
$ python3 dice.py
Would you like to roll (y/n)? n

You chose: n
$ python3 dice.py
Would you like to roll (y/n)? yes
Please enter only y or n
Would you like to roll (y/n)? maybe
Please enter only y or n
Would you like to roll (y/n)? puppies!
Please enter only y or n
Would you like to roll (y/n)? n

You chose: n

Keep in mind that the code that prints "You chose:" should be in main, not get_yes_or_no, since get_yes_or_no is returning the user’s choice to main.

4. Prompt the user to enter a value between two numbers

Next, write a function that displays a message to the user and then asks them to enter an integer between two values low and hi. This function will be used for asking the the target value of the game.

Implement the get_value_between(msg, low, hi) function using the prototype below.

def get_value_between(msg, low, hi):
    """
    Shows a message and then prompts the user to enter a number between
    low and hi (inclusive) and returns the user's choice.
    Re-prompts the user until a valid choice is made.

    msg: (str) message to display to the user
    low: (int) the smallest allowable value
    hi: (int) the largest allowable value

    returns: (int) the user's selected value
    """

This function should first display the message, i.e. the msg parameter.

Then it should (separately) prompt the user to enter a number between low and hi (using the values of low and hi that are passed as arguments to the function) and read the value that the user enters; see the examples below.

You may assume that the user will enter an integer, but if the user enters an invalid choice, i.e. a number less than low or greater than hi, print an error message and prompt the user repeatedly until they enter a valid choice.

4.1. Test the get_value_between function in main

Check that your function works correctly by calling it from main with "What is the target value?" as the message, 7 as low and 21 as hi. You should comment out the call to get_yes_or_no in main for testing get_value_between.

Run your program to test your function. You should see similar outputs as below (user inputs in bold):

$ python3 dice.py
What is the target value?
Enter a number between 7 and 21: 9

You chose: 9
$ python3 dice.py
What is the target value?
Enter a number between 7 and 21: 21

You chose: 21
$ python3 dice.py
What is the target value?
Enter a number between 7 and 21: 6
Enter a number between 7 and 21: 51
Enter a number between 7 and 21: 11

You chose: 11

As above, the code that prints "You chose:" should be in main, not get_value_between, which is returning the user’s choice to main.

5. Simulate rolling the die and updating the score

Now we’re ready to start implementing parts of the game!

Write a function that does the following:

  • Show the user their score, their opponent’s score, and the target value

  • Ask if they want to roll the die

  • If so, generate a random number to simulate rolling the die and show the value

  • Return the value that they rolled, or 0 if they didn’t roll

Implement the roll(score, opp, target) function using the prototype below.

def roll(score, opp, target):
    """
    Shows the current scores and target, then asks the user if they want
    to roll the die. If so, simulates rolling the die and displays the
    result. Then returns the rolled value, or 0 if the player did not roll.

    score: (int) the player's current score
    opp: (int) the opponent's current score
    target: (int) the target value

    returns: (int) the rolled value, or 0 if the player did not roll
    """

When asking the user whether they want to roll the die, your roll function must call the get_yes_or_no function that you implemented above.

To simulate rolling the die, your program should generate a random int between 1 and 6, inclusive.

Note that this function must both display and return the value the player rolled, or return 0 if they did not roll.

5.1. Test the roll function in main

In your main function, comment out any calls to get_yes_or_no and get_value_between, and test the roll function by calling it with score = 5, opp = 6, and target = 10.

Run your program to test your function. You should see similar outputs as below, keeping in mind that there may be slight differences depending on what value is rolled:

$ python3 dice.py
Your score is 5; your opponent's score is 6; the target is 10
Would you like to roll (y/n)? y
You rolled 3

Function returned 3
$ python3 dice.py
Your score is 5; your opponent's score is 6; the target is 10
Would you like to roll (y/n)? banana
Please enter only y or n
Would you like to roll (y/n)? y
You rolled 5

Function returned 5

As above, the code that prints "Function returned…​" should be in main, not roll.

In the example below, the player chooses not to roll, so the roll function returns 0, and does not show "You rolled…​":

$ python3 dice.py
Your score is 5; your opponent's score is 6; the target is 10
Would you like to roll (y/n)? n

Function returned 0

6. Playing the game

Now it’s time to implement the game!

Write a function that allows the two players to play the game by doing the following in a loop:

  1. Call the the roll function for Player 1

  2. Update Player 1’s score

  3. Show Player 1’s current score

  4. If they exceeded the target value, indicate that Player 2 won the game and return

  5. Otherwise, call the roll function for Player 2

  6. Update Player 2’s score

  7. Show Player 2’s current score

  8. If they exceeded the target value, indicate that Player 1 won the game and return

  9. If both players' scores equal the target, indicate that the game is a draw and return

Implement the play_game(target) function using the prototype below.

def play_game(target):
    """
    Play the game until someone wins

    target: (int) the target value

    returns: none
    """

Aside from using the roll function, you will need to initialize and update variables in this function to keep track of various aspects of the game.

6.1. Test the play_game function in main

Edit the main function to remove the call to roll and instead call play_game with target = 10.

Run the program and your output should be similar to what is shown in the sample program run, though the values will be different depending on the random numbers that are generated.

Note that at this point your program does not yet ask the user if they want to play again, so you should only see one game being played.

You may notice while playing the game that it is possible to achieve a state of "deadlock" in which neither player ever rolls so the game never ends. This is not something you need to address in implementing this game.

7. Finishing the main function

We’re almost done! The last step is to modify the main function so that it does the following in a loop:

  • Use the get_value_between function to ask for the target value, which should be between 7 and 21

  • Use the play_game function to play the game with the selected target value

  • After the game is over, use the get_yes_or_no function to see if the user wants to play again; if so, loop again

Now when you run your program, you should see something like what is shown in the sample program run, including asking if the user wants to play again.

Your output does not need to exactly match what is shown in the sample, but the logic of the gameplay should be the same. Ask for help if you’re unsure about how to handle any particular cases, though!

8. Requirements for dice.py

The code you submit for labs is expected to follow good style practices, and to meet one of the course standards, you’ll need to demonstrate good style on six or more of the lab assignments across the semester. To meet the good style expectations, you should:

  • Write a comment that describes the program as a whole using a comment at the top of the file.

  • All programs should have a main() function.

  • Use descriptive variable names.

  • Write code with readable line length, avoiding writing any lines of code that exceed 80 columns.

  • Add comments to explain complex code segments (as applicable).

  • Write a comment for each function (except main) that describes its purpose, parameters and return value.

Your program should meet the following requirements:

  1. Your program should correctly implement the get_yes_or_no function as described above.

  2. Your program should correctly implement the get_value_between function as described above.

  3. Your program should correctly implement the roll function as described above.

  4. Your program should implement the play_game function such that it correctly determines the winner if one player’s score exceeds the target.

  5. Your program should implement the play_game function such that it correctly ends in a draw if both players' scores equal the target.

  6. Your main function should correctly use get_value_between for getting the target value, correctly use play_game for playing a single game, correctly use get_yes_or_no for asking if the user wants to play again, and correctly playing again (or not) depending on their response.

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!