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
whileloops. -
Learn to use the
randomlibrary 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:
-
Draw the correct stack diagram.
-
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
For testing, you might want to instead try something like:
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
inputin 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_introfunction 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_againfunction 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:
|
Your program should meet the following requirements:
-
Your program should implement both the
print_introand theroll_one_diefunction with the same number of parameters, the same return type, and the behavior described above. -
Your program should implement the
get_play_againfunction 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 returningTrueorFalse, respectively). -
Your program should implement the
roll_for_playerfunction 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. -
Your program should implement the
play_one_gamefunction 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)
-
-
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 sayYes, keep playing more full games. After they sayNo, 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.pyto a new file namedchicago_extra.pyusing thecpcommand. -
Make your optional changes to the
chicago_extra.pyfile, leaving yourchicago.pyfile ready for grading.
Some things you might try are:
-
Players roll three dice instead of two, and they score if any two of the three reach the target value.
-
After rolling, the player can choose one of their dice to re-roll.
-
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
(
or
) 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
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!