CS21 Lab 6: More Advanced Functions
Due Sunday, March 13, by 11:59pm
NOTE: this is due at the end of Spring Break, but we strongly encourage you to try to finish before Spring Break starts (there won’t be any ninja sessions or office hours over break, so it’s best to get it done while you have access to help)
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
vim
, at the bottom left in the window, there is an indication of both the line and the column of the cursor.
-
Are your files in the correct place?
Make sure all programs are saved to your cs21/labs/06
directory! Files
outside that directory will not be graded.
$ update21 $ cd ~/cs21/labs/06 $ pwd /home/username/cs21/labs/06 $ ls Questions-06.txt (should see your program files here)
Function Comments
All functions should have a top-level comment! Please see our function example page if you are confused about writing function comments.
Are your files in the correct place?
Make sure all programs are saved to your cs21/labs/06
directory! Files
outside that directory will not be graded.
$ update21 $ cd ~/cs21/labs/06 $ pwd /home/username/cs21/labs/06 $ ls Questions-06.txt (should see your program files here)
Goals
-
Work with functions and lists
-
Work with mutable and immutable function parameters
-
Work with functions that return values
-
Work with functions used only for their side effect
Curse Breaker
\(\cdot\) \(\cdot\) \(\cdot\) \(\cdot\) \(\cdot\) \(\cdot\) \(\cdot\) \(\cdot\)
For this week’s lab we’re going to incrementally develop an implementation of a game we’ll call Curse Breaker, in which the player is attempting to break a magical curse on an artifact. The primary task is to identify the specific ordered combination of magical runes used on a given artifact; however, the evil wizard has done their best to make this process difficult.
The runes used are hidden by magic, so the curse breaker must attempt to remove the curse without knowing what the runes are. Unfortunately, the curse will absorb power from spells cast at it, so after a certain number of attempts the artifact will explode! Fortunately, the curse breaker can interpret the colors thrown off by unsuccessful attempts; a clever curse breaker can use this feedback to discover the runes and remove the curse before it’s too late.
For example, if the secret code is "sowilo isaz hagalaz ansuz", then:
-
a guess of "sowilo hagalaz ehwaz ansuz" has 2 matches.
-
a guess of "ehwaz sowilo ansuz isaz" has 0 matches.
-
a guess of "sowilo sowilo sowilo sowilo" has 1 match.
-
a guess of "hagalaz naudiz hagalaz naudiz" has 1 matches.
1. Printing lists of runes, generating lists of runes
Before writing functions to handle the main game mechanics, you should write some initial simple functions that generate and print lists of runes.
1.1. print_runes
The first function you write and test should be named
print_runes(rune_list)
. It should take a single list rune_list
of strings representing runes and nicely print the runes to the
screen. Put vertical bars ('|'
) between the runes to make the printout look nice.
For example,
-
if the input to
print_runes
was["sowilo", "sowilo", "ansuz", "ehwaz"]
, thenprint_runes
should print
| sowilo | sowilo | ansuz | ehwaz |
-
if the input to
print_runes
was["hagalaz", "sowilo", "isaz", "algiz", "ehwaz", "naudiz"]
, thenprint_runes
should print
| hagalaz | sowilo | isaz | algiz | ehwaz | naudiz |
Before moving onto other functions, test just this function by calling
it within main()
. For now, just make up the runes you want to
print out.
1.2. get_rune
Once you’ve written and tested print_runes
, write a function
get_rune(runes, number)
. This function should take a list of the possible
runes and a number, and should ask the user to choose a rune for the numbered
position (see example below). Note that the rune number is mostly so
when you print your prompts, you can let the user know what rune they’re
getting (this will make more sense when you write the next function).
If the user enters something that’s not a legal rune, ask again, and keep on
trying until the user enters a legal one. Once you’ve gotten one, return the
rune that the user entered (as a string). "Legal" here just means it’s a rune
that exists in the runes
list given as input (HINT: remember the in
operator works with lists). You’ll want to use print_runes
to remind the
user what the valid options are if they try to make an invalid choice (again,
see examples below)
This function is an example of input validation; in this case, valid just means a string in our list of runes.
Test this function by itself; for example, if your main
looks like this:
def main(): rune_list = ["ansuz", "isaz", "sowilo", "hagalaz", "algiz", "ehwaz", "naudiz"] r = get_rune(rune_list, 1) print("you picked: " + r)
Some sample ouput might look like this:
$ python3 cursebreaker.py Enter rune 1: hagalaz you picked: hagalaz $ python3 cursebreaker.py Enter rune 1: zoom zoom is not a valid rune. Please choose from the following runes: | ansuz | isaz | sowilo | hagalaz | algiz | ehwaz | naudiz | Enter rune 1: blarg blarg is not a valid rune. Please choose from the following runes: | ansuz | isaz | sowilo | hagalaz | algiz | ehwaz | naudiz | Enter rune 1: isaz you picked: isaz
1.3. get_guess
Once you’ve written and tested get_rune
, you can write the function
get_guess(runes, guess)
. This function should take one list of the possible
runes and a second list to store the user’s guesses in; the function should
then get 4 valid runes from the user, and place them into the guess
list.
Note that this function should call the previous one, it should not duplicate
the functionality of get_rune
. You’ll also want to use your print_runes
function.
Note that this function will not return anything, but rather will modify the
values in the guess
list it takes as input; you can then use print_runes
to
print out the guess back in main
.
See the example output below (user input in bold).
$ python3 cursebreaker.py Please enter four legal runes. Your choices are: | ansuz | isaz | sowilo | hagalaz | algiz | ehwaz | naudiz | Enter rune 1: sowilo Enter rune 2: pineapple pineapple is not a valid rune; please choose from the following runes: | ansuz | isaz | sowilo | hagalaz | algiz | ehwaz | naudiz | Enter rune 2: raido raido is not a valid rune; please choose from the following runes: | ansuz | isaz | sowilo | hagalaz | algiz | ehwaz | naudiz | Enter rune 2: ehwaz Enter rune 3: isaz Enter rune 4: zzzz zzzz is not a valid rune; please choose from the following runes: | ansuz | isaz | sowilo | hagalaz | algiz | ehwaz | naudiz | Enter rune 4: sowilo You guessed: | sowilo | ehwaz | isaz | sowilo | $ python3 cursebreaker.py Please enter four legal runes. Your choices are: | ansuz | isaz | sowilo | hagalaz | algiz | ehwaz | naudiz | Enter rune 1: sowilo Enter rune 2: ansuz Enter rune 3: algiz Enter rune 4: naudiz You guessed: | sowilo | ansuz | algiz | naudiz |
1.4. generate_code
Once you’ve written and tested get_guess(runes, guess)
, write and test a function
generate_code(runes, code)
. This function should take a list of possible runes and
a list of four empty strings; the second argument should be modified so that
after the function it contains four random runes from the list, subject to the
constraint that each rune in the list should be unique (i.e. there should be no
repeats in the 4 chosen runes). Use the choice
function from the random
library. The choice
function takes a list as input and returns a random
element from the list. (HINT: again, think about how you can use the in
operator to ensure you’re not about to add a rune that’s already in the code).
from random import choice ... L=[2,3,5,7,11] print(choice(L)) # prints out random number from L
Test your implementation of generate_code(runes)
by calling it
once or twice in main()
. Use print_runes
to print
out the runes.
1.5. check_guess
Next, write a function that will check how many matches the
player got with their guess. Note that we will be counting only "exact"
matches, meaning the correct rune in the correct position.
For example, if the code is
isaz ansuz sowilo hagalaz
and the user guesses
ansuz algiz sowilo ehwaz
, then only one match exists; the sowilo
matches,
but the ansuz
is in the guess is in the wrong place so it doesn’t count.
Write a function check_guess(code, guess)
that takes in two lists, each of
which should contain four runes, and returns a count of how many matches there
are. Remember, only count a match when the same rune is at the same place
(HINT: you probably don’t want to use the in
operator this time).
Be sure to test each function in your main()
program before moving
on.
2. Curse Breaker Game helper functions
By this point in the lab, you should have implemented and tested the following functions:
-
print_runes(rune_list)
-
get_rune(runes, number)
-
get_guess(runes, guess)
-
generate_code(runes, code)
-
check_guess(guess, code)
Before moving on to the final part of the implementation, it will be
helpful to write and test some simple helper functions. Each of these
functions will be quite simple, but they will help make the logic of your
program easier to follow, and help avoid cluttering up main
.
-
print_introduction()
should take no parameters and print out the rules to the game Curse Breaker. -
is_game_over(num_matches, turn)
takes two integer input parameters. This function should returnTrue
if the game is over; that is, if the user guessed all four exact matches, or if the user ran out of their thirteen turns. -
player_won(num_matches)
takes one integer input parameter. This function should returnTrue
if the player won the game.
It might seem a little strange to test the is_game_over()
or
player_won()
functions before writing the main part of the game. Just
call these functions with dummy variables to see if they return what
you expect them to return. It will be helpful to know these functions
work before it comes time to complete your program.
3. Putting it all together
We’re almost all done now. The fully finished cursebreaker.py
program
might have the primary steps in main()
as shown below. Think about
how to use all the helper functions you’ve just implemented to
accomplish each step.
-
Initialize necessary variables (e.g. legal runes, etc.)
-
Print out instructions to the user
-
Pick a secret code for the user to guess.
-
Until the user guesses the secret code or runs out of turns:
-
Show the current turn number
-
Ask the user for a guess
-
Compute the number of matches
-
Print out the number of matches
-
-
When the game is over:
-
If the player won, print out a congratulatory message.
-
If the player ran out of turns, print out a sympathetic message.
-
4. Sample output
As with the previous lab, your program’s output does not need to be identical to the following output, but you should have all the required functionality, and your program should display the information in a readable way.
Here is sample output from a complete program: Sample Output
5. Answer the Questionnaire
Each lab will have a short questionnaire at the end. Please edit
the Questions-06.txt
file in your cs21/labs/06
directory
and answer the questions in that file.
Once you’re done with that, run handin21
again.
6. Optional Extra Challenges
This is an optional extra challenge. There are lots of fun
extensions you could add to cursebreaker.py
.
-
Allow the user to choose how many turns the game takes.
-
Allow the user to choose how long the code is.
-
Allow the user to change how many runes you can guess from.
-
Implement a computer player who will make guesses instead of the user.
-
Anything else you can think of? Let us know if you come up with something interesting!
Answer the Questionnaire
Each lab will have a short questionnaire at the end. Please edit
the Questions-06.txt
file in your cs21/labs/06
directory
and answer the questions in that file.
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.
When Remotely logged in
When you are ssh’ed into the CS labs, first quit any applications you are
running, like vim
, then simply type exit
at the prompt in your terminal
window to disconnect.
When Physically logged in
When you are in a CS lab logged into a CS machine. First quit any applications you are running, like 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!