Tutorial 2: Mastermind
Objectives
What we will practice today: Operations on lists,
tuples iteration with for
and while
Setup: before you start
- Launch Spyder (from the Applications/Programming menu). If you see a “Spyder update” message, just click OK and ignore it.
- Create a new project, called
Tutorial_2
(using “New Project” from the “Projects” menu). This will ensure that files from last week (and future weeks) do not get mixed up.
Exercises
The aim of this tutorial is to implement the code-breaking game Mastermind. In this game, one player (the codemaker) chooses a secret code consisting of four pins of different colors, and the other player (the codebreaker) has to guess the pattern through several rounds, each time cleverly using the feedback she has received from the codemaker.
We will explain the game in detail below, but it also has a Wikipedia page.
Create a new program file called
mastermind.py
. Make sure you get the filename right, or
else the testing code won’t work. We will be using the
random
module in this tutorial, so you must add the
line
import random
near the top of your mastermind.py
file.
Exercise 1: Loops
Mastermind is played using ‘codes’. A code is a list of four
elements (“colors”) which each are chosen among the six colors
'RED'
, 'GREEN'
, 'BLUE'
,
'PURPLE'
, 'BROWN'
, and 'YELLOW'
(with repetitions).
We will use a global variable for the colors. Add
the following line to your file mastermind.py
:
= ['RED', 'GREEN', 'BLUE', 'PURPLE', 'BROWN', 'YELLOW'] COLORS
Warning: we use the American
spelling “colors”, and not the British “colours”, throughout this
tutorial. Be careful to follow this convention: if you try to use
COLOURS
, then Python won’t know what this is!
Solving the exercises on this sheet requires using loops in Python.
Thus, we will now create a function that uses a for
loop to
check if the given color exists in our list of COLORS.
Here’s a start:
def input_color(color):
"""Return True if the given input color can be found in COLORS."""
# Use a for-loop to iterate over the list of COLORS.
for i in range(6):
pass # remove this line and replace with your own code
You might prefer to iterate directly over the COLORS
list, rather than using indices!
Testing
Test your code in the console, after running
mastermind.py
. Make sure you can reproduce the following
examples:
1]: input_color('BROWN')
In [1]: True
Out[
2]: input_color('WHITE')
In [2]: False
Out[
3]: input_color('CUCUMBER')
In [3]: False Out[
Now you can upload your solution using the form below, for further automatic testing.
Exercise 2: Create a random code
We will be playing our game against the computer, so the first thing we need is a function which can create a random code.
Now define a new function create_code
(in mastermind.py
) which takes no arguments and
return
s a 4-element list of strings randomly chosen among
the six colors above.
Here’s a start:
def create_code():
"""Return 4-element list of strings randomly chosen from
COLORS with repetition.
"""
pass # remove this line and replace with your own code
Hints:
- Remember to
import random
. - if
s
is a list, thenrandom.choice(s)
returns a random element ofs
. - if
s
is a list, then commands.append(a)
will add elementa
as the last element of the list.
Testing
Test your code in the console, after running
mastermind.py
. Here’s what your output should look like
(but of course, it’s random, so it will not exactly look like
this):
4]: create_code()
In [4]: ['RED', 'RED', 'GREEN', 'BROWN']
Out[
5]: create_code()
In [5]: ['PURPLE', 'YELLOW', 'PURPLE', 'BLUE'] Out[
Upload your solution using the form below for further automatic testing.
Exercise 3: Guess a code
Next we implement functions which allow us to guess a code and receive feedback from the codemaker. The feedback is given as follows:
- For each pin of the right color in the
correct position, the codemaker gives a
'BLACK'
pin. - For each pin of a right color, but in the
wrong position, we receive a
'WHITE'
pin.
For example, if the code is
['RED', 'RED', 'GREEN', 'YELLOW']
and we guess
['RED', 'YELLOW', 'YELLOW', 'BROWN']
, then we receive one
black pin (for guessing the first 'RED'
) and one white pin
(for guessing the 'YELLOW'
in the wrong position; note that
the second YELLOW
guess doesn’t score an extra
pin, as there is only one YELLOW
pin in the code).
We will first take care of the black pins. Define a
new function black_pins
(in mastermind.py
)
which
- takes two arguments, a
guess
and acode
; and - the number of black pins that we receive for the guess.
That is, we expect the following behavior:
6]: black_pins(['RED', 'BLUE', 'YELLOW', 'BROWN'], ['RED', 'RED', 'GREEN', 'YELLOW'])
In [6]: 1
Out[
7]: black_pins(['RED', 'BLUE', 'BROWN', 'YELLOW'], ['RED', 'RED', 'GREEN', 'YELLOW'])
In [7]: 2 Out[
Here is the start of your function:
def black_pins(guess, code):
"""guess, code: 4-element lists of strings from COLORS
Returns the number of black pins, determined by the standard
Mastermind rules
"""
pass # remove this line and replace with your own code
Testing
Test your code in the console, after running
mastermind.py
. Start by reproducing the examples above
(exactly). Now think of some other tests to make sure your code is
correct.
Finally, upload your solution using the form below, and it will be tested automatically.
Exercise 4: Guess a code, suite
Now we write a function to determine the number of all pins (black and white) and use it (together with the function from the previous exercise) to find the number of white pins. Remember that according to the Mastermind rules, we receive a pin for every guess of a right color but maybe in a wrong position. That is, we have to compare the modified lists as unordered lists.
Define a new function score_guess
(in
mastermind.py
) that
- takes two arguments, a
guess
and acode
; - runs
black_pins
to determine the number of black pins; - computes the number of all pins, that is the number of colors we
guessed correctly (regardless of their position), for example, as
follows:
- for each color, you can find the number of correct guesses of the color as the minimum of the number of occurences of the color in the code and in the guess;
- the total number of pins will be the sum of these minimums over all colors;
- substract the number of black pins from the total number of pins to get the number of white pins
- and then
return
s a tuple(black, white)
containing the number ofBLACK
andWHITE
pins in the feedback.
Here’s a start:
def score_guess(guess, code):
"""guess, code: 4-element lists of strings
Return (black, white) where
black is the number of black pins (exact matches), and
white is the number of white pins (correct colors in wrong places)
"""
= black_pins(guess, code)
black # remove this line and complete the function with your own code
Here’s what we expect to happen in the console, once you’ve run
mastermind.py
:
8]: score_guess(['RED', 'GREEN', 'YELLOW', 'BLUE'], ['RED', 'YELLOW', 'BROWN', 'RED'])
In [8]: (1, 1)
Out[
9]: score_guess(['RED', 'GREEN', 'YELLOW', 'YELLOW'], ['RED', 'YELLOW', 'BROWN', 'RED'])
In [9]: (1, 1)
Out[
10]: score_guess(['RED', 'RED', 'RED', 'YELLOW'], ['RED', 'YELLOW', 'BROWN', 'RED'])
In [10]: (1, 2)
Out[
11]: score_guess(['RED', 'YELLOW', 'BROWN', 'RED'], ['RED', 'RED', 'RED', 'YELLOW'])
In [11]: (1, 2) Out[
Hints:
- There is a built-in function
count
in Python, see the docs.; - Example run:
- let’s say the function is called by:
score_guess(['RED', 'RED', 'GREEN', 'YELLOW'], ['RED', 'GREEN', 'YELLOW', 'BROWN'])
; - then, we first run
black_pins(['RED', 'RED', 'GREEN', 'YELLOW'], ['RED', 'GREEN', 'YELLOW', 'BROWN'])
which will return 1; - for each color in COLORS, count occurences in guess and code: for RED we get 2 and 1, for GREEN 1 and 1, for BLUE 0 and 0, for PURPLE 0 and 0, for BROWN 0 and 1, and for YELLOW 1 and 1;
- for each COLOR, we take the minimal value, thus we get 1, 1, 0, 0, 0 and 1 and take the sum of those which is 3;
- three is the total number of pins that will be shown, however, as we have already calculated that we return 1 black pin, the number of white pins is 2.
- let’s say the function is called by:
Testing
Now test your solution. Start by trying the four examples above, and make sure that you reproduce the output exactly. Then, think of some new tests of your own.
Finally, upload your solution to the form below for further automatic testing.
Exercise 5: User input
We will also need a function which lets the user input a guess.
Define a function input_guess
which
- prints
Enter your guess:
and lets the user input four colors from COLORS, - prompting the user first for a
1st color:
, then a2nd color:
etc., - and returns the guess as a list of four colors.
Your function needs to make sure that the user inputs colors which are contained in COLORS. If the user enters a color that is not in COLORS, then your program should ask them to try again.
Complete the following function, adding it to your
program mastermind.py
:
def input_guess():
"""Input four colors from COLORS and return as list."""
pass # remove this line and replace with your code
Hints:
- Hit
ctrl-C
in the console if your code “hangs” on an input. - Use the
str_with_suffix
function from last week to convert numbers to ordinal strings. - Use your function input_color to check if the entered color is correct.
Testing
Test your code in the console, after running
mastermind.py
:
12]: input_guess()
In [
Enter your guess:
1st color: RED
2nd color: YELLOW
3rd color: MAUVE
input a color from the list ['RED', 'GREEN', 'BLUE', 'PURPLE', 'BROWN', 'YELLOW']
Please
3rd color: BROWN
4th color: RED
12]: ['RED', 'YELLOW', 'BROWN', 'RED'] Out[
Upload your solution to the form below, it will be tested automatically.
Exercise 6: Playing one round of the game
We are now ready to implement one round of the Mastermind game.
Define a function one_round
(in
mastermind.py
) which
- uses
input_guess
to request input from the user, - uses
score_guess
to determine the number of black and white pins to award, - prints the number of black and white pins,
- and
return
sTrue
if the user has won, andFalse
otherwise.
Complete the following function, adding it to your
program mastermind.py
:
def one_round(code):
"""Input guess, score guess, print result, and return True iff
user has won.
"""
pass # remove this line and replace with your code
Testing
Test your code in the console, after running
mastermind.py
. First, make sure you can reproduce the
following examples exactly:
13]: one_round(['YELLOW', 'RED', 'BROWN', 'RED'])
In [
Enter your guess:
1st color: RED
2nd color: RED
3rd color: RED
4th color: BLUE
1 black, 1 white
Score: 13]: False
Out[
14]: one_round(['YELLOW', 'RED', 'BROWN', 'RED'])
In [
Enter your guess:
1st color: YELLOW
2nd color: RED
3rd color: BROWN
4th color: RED
4 black, 0 white
Score: 14]: True Out[
Now see if you can think of some more tests to catch possible bugs.
Finally, upload your solution using the form below for further automatic testing.
Exercise 7: Mastermind!
Now we can finally bind things together to play Mastermind against the computer.
Define a function play_mastermind
(in
mastermind.py
) that
- takes a code as input,
- lets the user play in rounds, keeping track of how many rounds have been played,
- until the user has won, in which case it prints
You win!
Here’s a start:
def play_mastermind(code):
"""Let user guess the code in rounds."""
# Hint: use a while loop!
pass # remove this line and replace with your own code
Here’s what we expect to happen in the console, once you’ve run your code:
15]: play_mastermind(['PURPLE', 'GREEN', 'YELLOW', 'BLUE'])
In [
1
Round
Enter your guess:
1st color: PURPLE
2nd color: BLUE
3rd color: YELLOW
4th color: BROWN
2 black 1 white
Score:
2
Round
Enter your guess:
1st color: PURPLE
2nd color: GREEN
3rd color: YELLOW
4th color: BLUE
4 black 0 white
Score: ! You win
Testing
Test your code using the examples above in the
console, after running mastermind.py
. Make sure you can
reproduce them exactly! Can you think of any more useful tests!
Finally, upload your solution using the form below, it will be tested automatically.
Extension Exercise 8: Making things more user-friendly
As you may have noticed, entering color guesses as the strings
YELLOW
, GREEN
, etc. is rather tiresome. We
would like to make our Mastermind program more user-friendly by
accepting inputs which do not conform to our specification but where it
is clear what the user meant.
To be precise, we want to accept unambiguous prefixes of
color names: there is only one color starting with Y
, so we
will accept Y
, YE
, YEL
,
YELL
, and YELLO
, all to stand for
YELLOW
. On the other hand, we cannot accept B
,
since this is the start of both BLUE
and
BROWN
; but BL
and BR
would be
unambiguous.
Copy your mastermind.py
into a new
file, mastermind_plus.py
; this is where you will program
your extensions to Mastermind.
Switching to mastermind_plus.py
,
change your input_guess
function so that
it accepts unambiguous prefixes of color names. Any accepted input must
be converted to the corresponding color names before being
return
ed.
Here’s what we expect to happen in the console, once you’ve run your code:
16]: input_guess()
In [
Enter your guess:
1st color: Y
2nd color: B
input an unambiguous prefix of a color from the list
Please 'RED', 'GREEN', 'BLUE', 'PURPLE', 'BROWN', 'YELLOW']
[
2nd color: BR
3rd color: BLU
4th color: R
16]: ['YELLOW', 'BROWN', 'BLUE', 'RED'] Out[
Hints:
- Python string objects have a useful method called
startswith
. Iffull
andprefix
are strings, thenfull.startswith(prefix)
will evaluate toTrue
iffull
starts with the substringprefix
, andFalse
otherwise.
Testing
Now test your code in the console, after running
mastermind_plus.py
.
If you use create_code
instead of giving a code as
input, you can now play against the computer. How many
rounds does it take you to win? (It has been shown that 5 rounds are
enough when you play optimally!)
Upload your solution to the form below.
Extension Exercise 9: Generalized Mastermind
There is nothing special about the six colors 'RED'
,
'GREEN'
, 'BLUE'
, 'PURPLE'
,
'BROWN'
, and 'YELLOW'
we have chosen for our
Mastermind implementation; any other set of six different items will do.
In fact, also the number six here is somewhat arbitrary; there
exist versions of Mastermind with five colors or eight. Finally, the
fact that our implementation uses codes consisting of four
elements is also arbitrary, and there are again versions of Mastermind
which use codes of three, five or six elements.
Just to stress the point, Disney published a version of Mastermind in the 70s which uses codes of three out of five Disney characters (here are some pictures).
The difficulty of solving the exercises below will depend on the way you have implemented your solutions to the mandatory exercises; the easier it is to generalize things, the better your code quality.
Change your code so that you can replace
COLORS
list with any list (try six Norse Gods'Odin'
,'Thor'
,'Freya'
,'Skadi'
, 546'Frigg'
, and'Heimdall'
, then try to add'Loki'
).Change your Mastermind so that codes (and guesses) are of length equal to global constans
CODE_LENGTH
instead of four. Check that everything still works as expected.
Testing
It is important to test your code.
Upload your solution in mastermind_plus.py
using the
form below.
Difficult Extension Exercise 10: Pitting the computer against itself!
Playing Mastermind against the computer quickly gets boring, so let’s see if we can program the computer to play against itself! (Before going further, revert any changes from the previous exercise to go back to the original Mastermind version.)
There’s an algorithm for solving Mastermind, first described by Don Knuth, which works as follows:
Try a guess
x
,x
,y
,y
, wherex
andy
are two different colors.Find the set \(S\) of all possible codes: all codes which would get you the score which your current guess did.
For each possible guess \(g\) and each black-white answer pin combination \(a\), find the number of guesses which would be eliminated from \(S\) if we used \(g\) as next guess and had \(a\) as answer. The score of \(g\) is the minimum of these numbers over all possible answer-pin combinations.
Take one of the \(g\) with maximum score and use as next guess.
Knuth
has shown that this algorithm always reaches a solution after at
most five steps. Define a function
autoplay_mastermind
which implements Knuth’s algorithm.
Hints:
imagine that the wanted code is one of the possible permutations on 4 elements of your colors. Steps 2, 3 and 4 try to find a consistent set of possible solutions by comparing guesses against each other and counting the resulting scores.
step three can also be described in the following way (found here: https://stackoverflow.com/questions/62430071/donald-knuth-algorithm-mastermind):
“Take the set of untried codes, and call it \(T\).
Iterate over \(T\), considering each code as a guess, \(g\).
For each \(g\), iterate over \(T\) again considering each code as a possible true hidden code, \(c\).
Calculate the black-white peg score produced by guessing \(g\) if the real code is \(c\). Call it \(s\).
Keep a little table of possible scores, and as you iterate over the possible \(c\), keep track of how many codes produce each score. That is, how many choices of \(c\) produce two-blacks-one-white, how many produce two-blacks-two-whites, and so on.
When you’ve considered all possible codes (for that \(g\)) consider the score that came up the most often. You might call that the least informative possible result of guessing \(g\). That is \(g\)’s score; the lower it is, the better.
As you iterate over \(g\), keep track of the guess with the lowest score. That’s the guess to make.”
Upload your solution using the form below (no automatic tests for this exercise though).