CS21 Lab 5: More Advanced Functions

Due Saturday, Oct 9, before midnight

Programming Tips

As you write your first programs, start using good programming practices now:

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

    • Atom shows the column number in the lower left and draws a vertical line down the editing window at 80 characters.

    • In emacs, at the bottom, center of 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.

Are your files in the correct place?

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

$ update21
$ cd ~/cs21/labs/05
$ pwd
/home/username/cs21/labs/05
$ ls
Questions-05.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

For this week’s lab we’re going to incrementally develop an implementation of the game Mastermind.

You can also play Mastermind online. We encourage you to play it just a few times until after you’ve finished the lab :).

Our version of the rules.

In this game, the computer generates a secret "code", which is a list of four colors e.g. "green green blue red". There are six possible colors: red, yellow, blue, green, orange, and purple.

The object of the game is to win. You win by guessing the secret code. Each turn, the player enters a guess for the code e.g. "blue orange yellow blue". The computer then reports

  1. how many exact matches the player had, and

  2. how many inexact matches the player had.

An inexact match is when the player guesses the color correctly, but not the position, and an exact match is when the player guesses a color correctly and puts it in the correct position.

The player has ten turns to guess the secret code. If they do, they win! Otherwise, the computer wins.

For example, if the secret code is "green green blue red", then:

  • a guess of "green purple blue purple" has 2 exact matches and 0 inexact matches.

  • a guess of "red blue green purple" has 0 exact matches and 3 inexact matches.

  • a guess of "green green green green" has 2 exact and 0 inexact matches.

  • a guess of "yellow orange yellow purple" has 0 exact and 0 inexact matches.

1. Printing lists of colors, generating lists of colors

Before writing functions to handle the main game mechanics, you should write some initial simple functions that generate and print lists of colors.

The first function you write and test should be named print_colors(color_list). It should take a single list color_list of strings representing colors and nicely print the colors to the screen. Put commas between the colors to make the printout look nice. For example,

  • if the input to print_colors was ["green", "green", "blue", "red"], then print_colors should print

green, green, blue, red
  • if the input to print_colors was ["red", "orange", "yellow", "green", "blue", "purple"], then print_colors should print

red, orange, yellow, green, blue, purple

Before moving onto other functions, test just this function by calling it within main(). For now, just make up the colors you want to print out.

Once you’ve written and tested print_colors, write a function get_guess(colors). This function should take a list of the six possible colors, and ask the user for four legal colors. Keep on asking for a color until the user enters a legal one. Store these four colors in a list, and return the list once the user has entered four legal colors.

See the example code below (user input in bold).

$ python3 mastermind.py
Please enter four legal colors.  Your choices are:
red, orange, yellow, green, blue, purple
Enter color 1: white
white is not a valid color.
Please choose from the following colors:
red, orange, yellow, green, blue, purple

Enter color 1: blue
Enter color 2: blue,
blue, is not a valid color.
Please choose from the following colors:
red, orange, yellow, green, blue, purple

Enter color 2: blue
Enter color 3: green
Enter color 4: black
black is not a valid color.
Please choose from the following colors:
red, orange, yellow, green, blue, purple

Enter color 4: red

You guessed:
blue, blue, green, red

Once you’ve written and tested get_guess(colors), write and test a function generate_code(colors). This function should take a list of the six possible colors and return a list of four random colors. Use the choice function from the random library. The choice function takes a list as input and returns a random element from the list.

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(colors) by calling it once or twice in main(). Use print_colors to print out the colors.`

By the end of this part of the lab, you should have implemented and tested the following functions:

  • print_colors(color_list)

  • get_guess(colors)

  • generate_code(colors)

Once you’ve implemented these functions, it’s time to move onto the next part of the lab!

2. Checking the user’s guess

In this section, you will write functions that will check how many exact and inexact matches the user got. Note: figuring out how many matches the user gets is sublte. If not done correctly, it is easy to overcount. For example, if the code is blue blue green red and the user guesses green orange green purple, then only one exact match should be returned, not one exact and one inexact. You’ll need to carefully keep track of the matches to avoid overcounting.

To count the number of exact and inexact matches, write two separate functions.

  • exact_matches(secret_code, guess, status)

  • inexact_matches(secret_code, guess, status)

exact_matches should return an integer representing the number of exact matches in the string. Similarly, inexact_matches also returns an integer, representing the number of inexact matches in the string.

status should be a list of four strings. Initially each string is an empty string. As a color in the secret_code list is matched, you should keep track of this by changing the corresponding string in status.

The exact_matches(secret_code, guess, status) function takes three input parameters. secret_code and guess are the target code and the user’s guess. Each is a list of four colors. status should initially be a list of four empty strings ["", "", "", ""]. As you determine where the exact matches are in guess, mark that element of the status list as "exact". For example, if the secret_code is blue blue green green and the guess is blue green green red, then exact_matches should return two, and status should change to `["exact", "", "exact", ""].

The inexact_matches(secret_code, guess, status) function takes the same three input paramters, and computes the number of inexact matches between secret_code and guess. To compute an inexact match, consider each color in guess. If it was not an exact match, scan through the colors in the secret_code list. If there are any colors in secret_code that (i) equal the current guess color and (ii) have not already been matched, you found an inexact match! Make sure to update the status list so the color in secret_code that you just matched doesn’t get matched twice. For example, if the code is [blue blue green green], the guess is [blue green green red], and the status is ["exact", "", "exact", ""] then

  • First, look at blue. The first element of status is "exact", so we’ve already matched blue. Don’t try to find an inexact match since blue was already matched.

  • Second, try to match green to a color in code. secret_code contains two greens. The first was already matched, but from status we know that the second has not yet been matched. Increment one to your count of inexact matches, and change status to ["exact", "", "exact", "inexact"]. This status indicates that the last color in secret_code has been matched with an inexact match (to the second color in guess).

  • Next, look at the third color in guess. The third element of status is "exact", so we’ve already matched this green. Don’t try to find an inexact match since green was already matched.

  • Finally, try to match red to a color in the code. There is no match.

In the example above, inexact_matches should return 1, and status should change to ["exact", "", "exact", "inexact"].

Here is a second example:

  • Suppose the secret code is red blue yellow purple, the guess is orange green red red, and status is initially ["","","",""].

  • When exact_matches gets called on the inputs above, it should return zero (since there are no exact matches), and status should remain ["", "", "", ""].

  • When inexact_matches gets called on the above inputs,

    • orange and green are first checked, but don’t appear in secret_code.

    • Next, it checks red and finds an inexact match with the first color in secret_code. The number of inexact matches is incremented to 1, and status becomes ["inexact", "", "", ""] (since red matches the first color in secret_code.

    • Finally, it checks the second red. This matches the first color in secret_code; however, since from status we know that the first color in secret_code has already been matched, we can’t use it (otherwise we’ll overcount). The remaining colors in secret_code are not red, so we don’t find an additional inexact match. inexact_matches returns 1, and status changes to ["inexact", "", "", ""].

To recap, to implement inexact_matches(secret_code, guess, status) we encourage your code to follow the following outline:

  • For each guessed color

    • if that color is an exact match, skip it (it is already accounted for)

    • otherwise, check each color in secret_code sequentially

      • if the guessed color matches this color in the secret_code and this color in the secret_code hasn’t been matched already, then you found another inexact match! Update your count of inexact matches, and update status to show that this color in secret_code has been matched.

Be sure to test each function in your main() program before moving on. Do exact_matches and inexact_matches both return the correct number of matches? Do they update status correctly? If so, you are ready to complete the full mastermind program!

2.1. The Python break statement

While implementing inexact_matches, you might find that you’d like to exit a for loop or while loop early. Foruntately, python has just such a command: break. When Python encounters a break statement inside a for loop, it immediately exits the for loop. For example, consider the following code snippet:

print("before for loop")
for i in range(10):
  print(i)
  if(i >= 3):
    break
print("after loop")

The output of running this code is

before for loop
0
1
2
3
after loop

The break statement can be a useful way to exit a for loop early.

3. Mastermind game helper functions

Before moving on to the final part of the implementation, it will be helpful to write and test some simple helper functions.

  • print_introduction() should take no parameters and print out the rules to Mastermind.

  • is_game_over(num_exact_matches, turn) takes two integer input parameters. This function should return True if the game is over; that is, if the user guessed all four exact matches, or if the user ran out of their ten turns.

  • player_won(num_exact_matches) takes one integer input parameter. This function should return True 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.

4. Putting it all together

We’re almost all done now. The fully finished mastermind 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.

  1. Print out instructions to the user

  2. Pick a secret code for the user to guess.

  3. 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 exact and inexact matches

    • Print out the number of exact/inexact matches

  4. Check to see if the player won. If so, print out how many turns it took. If not, print out a sympathetic message.

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

$ python3 mastermind-jesb.py
Welcome to Mastermind!
Your goal is to guess a sequence of four colors
Each color can be blue, green, red, orange, purple, or yellow.
Then I'll tell you how many times you guessed the correct color
  in the correct position, and how many times you guessed the
  correct color, but in the wrong position.
You have ten turns to guess the correct sequence.
good luck!


Turn 1:
Please enter four legal colors.  Your choices are:
red, orange, yellow, green, blue, purple
Enter color 1: white
white is not a valid color.
Please choose from the following colors:
red, orange, yellow, green, blue, purple

Enter color 1: blue
Enter color 2: blue
Enter color 3: gren
gren is not a valid color.
Please choose from the following colors:
red, orange, yellow, green, blue, purple

Enter color 3: green
Enter color 4: green

You guessed:
blue, blue, green, green
There are 1 exact matches.
There are 1 inexact matches.

Turn 2:
Please enter four legal colors.  Your choices are:
red, orange, yellow, green, blue, purple
Enter color 1: red
Enter color 2: red
Enter color 3: orange
Enter color 4: orange

You guessed:
red, red, orange, orange
There are 0 exact matches.
There are 1 inexact matches.

Turn 3:
Please enter four legal colors.  Your choices are:
red, orange, yellow, green, blue, purple
Enter color 1: blue
Enter color 2: orange
Enter color 3: blue
Enter color 4: yellow

You guessed:
blue, orange, blue, yellow
There are 2 exact matches.
There are 0 inexact matches.

Turn 4:
Please enter four legal colors.  Your choices are:
red, orange, yellow, green, blue, purple
Enter color 1: blue
Enter color 2: purple
Enter color 3: blue
Enter color 4: red

You guessed:
blue, purple, blue, red
There are 4 exact matches.
There are 0 inexact matches.


The answer was:
blue, purple, blue, red
You won!
It took you 4 turns to get the correct answer.

6. Answer the Questionnaire

Each lab will have a short questionnaire at the end. Please edit the Questions-05.txt file in your cs21/labs/05 directory and answer the questions in that file.

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

7. Optional Extra Challenges

This is an optional extra challenge. There are lots of fun extensions you could add to mastermind.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 colors 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!

Turning in your labs…​

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, like 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!