CS21 Lab 4: Functions and While loops

Due Sunday, March 1, 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: lab4stack.pdf

You should submit your written solution directly to gradescope. (If this link doesn’t work, please let Kevin know. In that case, log in to gradescope and you should see an assignment called "Lab 4 Stack Diagram" that you can upload to.)

1.1. Requirements

Given the pdf handout above:

  1. Draw the correct stack diagram.

  2. Show the output of the program.

2. Programming Assignment: Chicago (Dice Game)

In this lab, we will be writing a single program that has the user play a dice game called Chicago (also known as "Rotation").

In our version of the game, there will be two players, a human player and a computer player. Each player begins with a score of 0. The game consists of multiple rounds (always 11 rounds in our version) in which each player rolls two dice and scores points if the sum of their dice matches the round’s target number.

For the first round, the target value is 2, and the target increases by 1 in each consecutive round up to a maximum of 12. As an example:

Both players start with a score of 0.

In the first round, the target value is 2.

Player 1 rolls a 1 and 4 (total 5). Since 5 doesn’t match 2, the player doesn’t score any points. Player 2 rolls a 6 and 2 (total 8). Since 8 doesn’t match 2, the player doesn’t score any points.

Both players still have a score of 0.


In the next round, the target value is now 3.

Player 1 rolls a 2 and 5 (total 7). Since 7 doesn’t match 3, the player doesn’t score any points. Player 2 rolls a 1 and 2 (total 3). Since 3 does match 3, the player adds 3 points to their score.

Now, player 1 has 0 points, and player 2 has 3 points.


In the next round, the target value is now 4.

Player 1 rolls a 2 and 2 (total 4). Since 4 does match 4, the player adds 4 points to their score. Player 2 rolls a 3 and 5 (total 8). Since 8 doesn’t match 4, the player doesn’t score any points.

Now, player 1 has 4 points, and player 2 has 3 points.


(The game continues on this way with rounds targeting 5, 6, 7, …​, 11, and 12.)

The player with the most points at the end (after the round with a target of 12) wins the game.

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 or two functions 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, we’ll tell you when it’s time to put all the functions together into a complete version of the game.

You’ll put all of your code for the rest of this lab in the file chicago.py.

Before we dive in to the details, let’s take a look at what the final running program looks like.

2.1. The print_intro function

In the sample output, you will notice that there is a welcome message displayed when the program is first run. Your message doesn’t have to be the same, but you should write a function called print_intro that takes no parameters and returns nothing. The function should print out a welcome message to the user. The function definition will look like:

def print_intro():

Add a call to print_intro in main and test running your program to verify that your introduction is being printed correctly.

3. Get a choice from the user

In the file named chicago.py, write a function named get_play_again that prompts a user via the input function about whether or not they’d like to play again. The get_play_again function does not take any parameters, and it should return a Boolean value (True if the user enters the string Yes or False if the user enters the string No).

If the user doesn’t enter Yes or No, your function should print a helpful error message and prompt the user to try again. The function should only return when the user enters Yes or No.

Reminder: Each of your functions is expected to have a comment that describes its purpose, parameters (and their types), return value (and its type), and any side effects it causes. We strongly suggest writing these comments as you go along, so if you haven’t already written your comment yet, go finish that off now! We have a reference example in case it helps.

3.1. Test get_play_again in main

To test get_play_again, you should have your main function call the get_play_again function and print out the value returned. Your main should something look like this:

def main():
    choice = get_play_again()
    print("get_play_again returned %s" % (choice))

You can now test to make sure the get_play_again function is working properly. Here are some examples of what your program should do at this point:

$ python3 chicago.py
Play again? Yes or No: Yes
get_play_again returned True

$ python3 chicago.py
Play again? Yes or No: No
get_play_again returned False

$ python3 chicago.py
Play again? Yes or No: apple
Sorry, you must enter either Yes or No.
Play again? Yes or No: Maybe
Sorry, you must enter either Yes or No.
Play again? Yes or No: YES
Sorry, you must enter either Yes or No.
Play again? Yes or No: Yes
get_play_again returned True

$ python3 chicago.py
Play again? Yes or No: No
get_play_again returned False
Remember that python strings can be compared with relational operators like == or < just like integers and floats:
$ python3
>>> "rock" == "paper"   # evaluates to False
>>> "rock" != "paper"   # evaluates to True

4. Choose a random number

Write a function called roll_one_die that generates and returns a random integer between 1 and 6. (You should use the randrange function, described in the subsection below, to help you do this.) Every integer from 1 to 6, including both 1 and 6, are values that roll_one_die could generate. Although this may be a short function, it’s a good idea to make this a separate function because it puts the randomness in your game into a single function. If for some reason you wanted to change the game in some way (e.g. maybe you roll a die with more than 6 sides), you only need to change this function and not the rest of your program.

The roll_one_die function does not take any input values (it has no parameters), but it should return an integer value. Its function definition will look like:

def roll_one_die():

4.1. Selecting a random integer from a range of integers

This subsection will remind you how the randrange function works. To use randrange, we import it from the random library.

The randrange function returns a random integer from a specific range. The inputs work just like the range function you’ve been using already. For example, randrange(10) returns a random integer from the range 0…​9. You can also provide it with two inputs: calling randrange(a, b) will return a random integer x such that a < = x < b. Calling randrange(2,5) will randomly select one integer from the integers 2,3,4.

Here’s a simple program that demonstrates how to use the randrange with the examples above:

from random import randrange

# When you run this program multiple times, you should get different,
# random results each time you run it.

def main():
    value = randrange(100) # randomly pick an integer from 0-99
    print(value)

    number = randrange(2, 11) # randomly pick an integer from 2-10
    print(number)

main()

In the roll_one_die function described above, you will use the randrange function to randomly select an integer between 1 and 6.

4.2. Test roll_one_die in main

Remember, we are solving this incrementally so there’s no need to preserve your main function as you develop the solution to this next step.

To test roll_one_die, you should have your main function call the roll_one_die function and print out the value returned. Your main should something look like this:

def main():
    number = roll_one_die()
    print("roll_one_die returned %d" % (number))

You can now test to make sure the roll_one_die function is working properly. Here are some examples of what your program should do at this point:

$ python3 chicago.py
roll_one_die returned 4
$ python3 chicago.py
roll_one_die returned 1
$ python3 chicago.py
roll_one_die returned 2

5. Taking one turn in the game

Next, let’s extend our program to add a function, roll_for_player that simulates one player taking a complete turn, including:

  • Rolling two dice

  • Determining the sum of their dice

  • Printing a message containing the name of the player, the values of the dice, and their sum

  • Checking to see if they score any points in the current round

  • Printing a message containing the name of the player and how many points they scored in this round

  • Returning the number of points they scored (which might be 0)

The roll_for_player function should take two inputs: a string name representing the name of the player who is rolling, and an integer target representing the current round’s target number. It should return the number of points the player scored from this roll. Based on the rules of the game, it’ll return either 0 (meaning the player didn’t score) or the target value (meaning the player rolled two dice whose sum matched that of the target). The function definition should look like:

def roll_for_player(name, target):

Note: Your roll_for_player implementation should call some of the functions you’ve already written and tested. Because you’ve tested these functions thoroughly, you can rely on them working the way you expect.

5.1. Test roll_for_player in main

After you write roll_for_player, you should test it in main:

def main():
    turnscore = roll_for_player("Jane", 4)
    print("Jane scored %d points this turn" % turnscore)

    turnscore = roll_for_player("Computer", 4)
    print("Computer scored %d points this turn" % turnscore)

Here are some example runs. Your main function does not need to run exactly like this, and your output does not need to look precisely like the example output.

When testing roll_for_player, your results will be random due to the behavior of roll_one_die. For testing purposes, you might find it helpful to adjust roll_for_player to temporarily pretend like the die roll was always a specific number. For example, you might have a line in roll_for_player that looks like:

roll1 = roll_one_die()

For testing, you might want to instead try something like:

# roll1 = roll_one_die()
roll1 = 2

This way, you get consistent results for your tests. Just don’t forget to actually call the random die rolling function before moving on.

6. Play the full game

Next, build a play_one_game function that plays one full game of Chicago between a human player and a player named "Computer". The play_one_game function should take one parameter, player_name, a string containing the name of the human player. The function doesn’t need to return anything — it will print the outcome of the game.

The general sequence of events in play_one_game should be:

  • Initialize scores for both players

  • Run through multiple game rounds, where each round progresses through target scores of 2, 3, 4, …​, 11, and 12.

  • Each round should state the current target score and prompt the user to press enter to start the round. (This can simply be a call to input in which you don’t save the resulting value the user types in.)

  • Roll for each player, updating their total scores as necessary

  • After all rounds are over, print the final score for each player and state which player won (note that a tie is possible)

6.1. Test play_one_game in main

There’s no need to preserve your main function from the last steps, but parts of what you wrote in the last step can be reused in this step if you find that helpful.

When you’re ready to test play_one_game, your main function needs to prompt for a player name, and then it can call play_one_game, passing the user’s name as the input parameter. From there, you should be able to fully test playing a full game, since all the details of a game are contained within play_one_game.

7. Sample output

Your program’s output does not need to be identical to the sample output shown earlier, but you should make sure to have all the required functionality and that your program displays the information in a readable way.

8. Completing the main function

Now that you’ve tested all the other functions, write some code in main to complete the rest of the program. Given the functions above, main will be relatively short, doing the following:

  • Call the print_intro function to display a welcome message.

  • Ask the name of the human player.

  • Play one full game.

  • After one full game, prompt the user asking if they want to play again, and if so, play another full game (your get_play_again function should be helpful here!).

  • Keep repeating the step above until the user says No.

9. Requirements

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 implement both the print_intro and the roll_one_die function with the same number of parameters, the same return type, and the behavior described above.

  2. Your program should implement the get_play_again function with the same number of parameters (in this case, none), the same return type (in this case, a Boolean), and the same behavior as described above (in this case, asking the user to type in "Yes" or "No", validating the input, and returning True or False, respectively).

  3. Your program should implement the roll_for_player function with the same number of parameters (a name and a target score), the same return type (number of points the player scored in this round), and the behavior described above.

  4. Your program should implement the play_one_game function with the same number of parameters (a player name) and the behavior described above. In particular, it needs to:

    • Print information at the start of the round with a pause for the user to press enter

    • Roll for each player and show each player’s updated score after rolling

    • Print final score information, including the name of the winner (or a tie)

  5. In main, your program should prompt for the name of the player and play at least one full game of Chicago. After playing a full game, it should continue prompting the user to ask if they want to play more games. As long as they say Yes, keep playing more full games. After they say No, exit the program.

10. OPTIONAL fun things to try

If you’d like to make your game more interesting, here are some fun things to try. These are optional, but if you’re interested in trying them:

  • Finish the required parts of the lab as described above first.

  • Preserve your working solution by copying chicago.py to a new file named chicago_extra.py using the cp command.

  • Make your optional changes to the chicago_extra.py file, leaving your chicago.py file ready for grading.

Some things you might try are:

  1. Players roll three dice instead of two, and they score if any two of the three reach the target value.

  2. After rolling, the player can choose one of their dice to re-roll.

  3. Allow for more than two players.

Do you have other ideas? Try them out! Make the game more fun for you. Just be sure you put your ideas in chicago_extra.py.

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!