Details
Due date
This assignment is due on Thursday, April 18th, at 11:30PM
Coverage
This HW is the first assignment in the form of a Mini Project (MP). The rest of HW assignments will be Mini Projects, where you will practice what you've learned in lectures, readings, and Tuesday labs to implement a working program, ranging from games, biology, physics, etc.
This Mini Project focuses on program decomposition with functions, user input with input
,
strings, lists, loops, and documentation with docstrings
all used collectively to implement an interactive game of Mastermind.
What to hand in
You will be handing in the following files for this Mini Project on CodePost as usual:
-
mp2_mastermind.py
. -
my_pytest_mp2.py
.
Do not hand in any of the test scripts we provide (test_mp2.py
).
In order for your files to work with the test scripts, make sure that there is
no code except for the functions (and docstrings) that we ask for. In
particular, there should be no top-level code that is outside a function,
because if there is then it will run when the tests run, which will make the
output very hard to understand. Remember that you can always |
The following is the expected structure for your program, which you can download as starter code here (again, note that there are no variables outside of your function definitions):
"""
Student name:
Date
Brief program overview (2-3 sentences)
<Additional reminders that should be removed>
"""
import random
# Function definitions each with valid """...""" docstrings, separated by two lines each
if __name__ == '__main__':
run_game()
Grading notes
Similar to HW1, this assignment is graded out of 30 points which include functional correctness, documentation with
docstrings for your file and each function, limiting variables only to those in your functions and __main__
branch,
following Python 3 conventions taught in lecture and the CS 1 Code Quality Guide,
and overall adherence to the spec.
For example, if your program outputs any input
prompts or print
statements differently than
specified here, you will be ineligible for full credit.
As described in the syllabus, your resulting MP2 grade will be out of 3 points, rounding (your score out of 30 / 10) to the nearest integer.You should not be implementing any behavior that is not specified.
Reminder: Mini Projects/HWs are strictly no collaboration. You are not allowed to look at other students' code, nor online solutions. Submissions which are flagged for suspicion of violating the CS 1 or Caltech Honor Code will be escalated to the BoC. We have many resources to help you succeed in this class, and you are encouraged to utilize them if you get stuck!
Documentation
From this point on, we expect you to write docstrings for all of your
functions. You can write short (one-line) docstrings for very simple
functions, but for most functions we expect you to write multiline docstrings
explaining what the function does (but not how it does it!), what the arguments
mean, and what the return value (if any) means. Also, if the function has some
other kind of effect (like reading from a file or printing to the terminal),
mention that too. You don’t have to write a long description, but try to write
enough so that someone who didn’t know what the function did could use it
correctly (similar to one using the help
function you practiced in HW1).
If you’re unclear on whether your docstrings are good enough, ask
the TAs or El during office hours.
Write the docstring prior to writing the function. Doing this can clarify your understanding of what the function is supposed to do. |
Using pytest
for testing
For this assignment (and all future assignments), we will be using the pytest
testing framework. This package provides the pytest
program as well as
Python modules which will make it much easier to test the code that you write.
In particular, this will make it easier for you (and your TA!) to figure out if
your code is correct or not.
Installation
To install the pytest
framework, open up a terminal and type this command:
$ pip3 install pytest
(The $
is just the prompt; don’t type that.)
In case you’re wondering, pip3
is the Python 3 package manager. Many
wonderful things can be installed using pip3
. [1]
To check that the installation succeeded, start up the Python interpreter and type:
>>> import pytest
If it gives you the prompt again without reporting any errors, you’ve installed
pytest
. If not, ask a TA for help or ask a question on Discord.
Most of the time, we will be using the pytest
program directly from the
command-line. To make sure that this program is available, type this:
$ pytest --help
If you see a long help message typed to your terminal, the program has been
installed.
If you see an error message, first try the following (you may have run into this requirement of adding python3 -m
when using pycodestyle
as well in Lab 01), otherwise ask a TA for help or ask a question on Discord.
>>> python3 -m pytest --help
If this fixes the error, you'll need to add python3 -m
before pytest program_name.py
when testing your programs.
Assuming that pytest
is installed, you can check which version is running by
typing:
$ pytest --version
The most current version is version 6.2.5.
Documentation
The pytest
documentation is here, but you don’t need to read it
(it may be more confusing than helpful to you at this stage). We will only be
using the most basic features of pytest
for the next few Tuesday Labs/Mini Projects.
Basic use
The most basic way to use pytest
is simply to have a Python module whose name
starts with test_
. (So you might have test_mycode.py
, for instance.)
Inside this module (which is also known as a test script) you put functions
whose names start with test_
. For instance, you might have:
# These functions would normally be defined in another module and imported here.
def double(x):
return x * 2
def triple(x):
return x * 3
def quadruple(x):
return x * 4
# These are the test functions.
def test_double():
assert double(42) == 84
def test_triple():
assert triple(42) == 126
def test_quadruple():
assert quadruple(42) == 168
Let’s say you saved this as test_mycode.py
. Then you could run it by typing
this at the terminal prompt:
$ pytest test_mycode.py
or
$ python3 -m pytest test_mycode.py
You should see output like this:
============================= test session starts ============================== platform darwin -- Python 3.9.0, pytest-6.1.1, py-1.9.0, pluggy-0.13.1 rootdir: /Users/mvanier/_/src/MCV/CS1-2020-prep/mike/assignments/2 collected 3 items test_mycode.py ... [100%] ============================== 3 passed in 0.01s ===============================
The collected 3 items
line means that there are three tests in the file.
After the test_mycode.py
you see three dots, which means that all of the
tests were executed and passed.
Let’s intentionally make a test fail. Change the last line of test_mycode.py
to:
assert quadruple(42) == 0
and rerun pytest test_mycode.py
. Here is the result:
============================= test session starts ============================== platform darwin -- Python 3.9.0, pytest-6.1.1, py-1.9.0, pluggy-0.13.1 rootdir: /Users/student/code collected 3 items test_mycode.py ..F [100%] =================================== FAILURES =================================== ________________________________ test_quadruple ________________________________ def test_quadruple(): > assert quadruple(42) == 0 E assert 168 == 0 E + where 168 = quadruple(42) test_mycode.py:20: AssertionError =========================== short test summary info ============================ FAILED test_mycode.py::test_quadruple - assert 168 == 0 ========================= 1 failed, 2 passed in 0.03s ==========================
This gives you a description of exactly what went wrong.
There is a lot more that you can do with pytest
. We will cover these extra
features when we need them.
MP2 Test Suite
Once you have written your mp2_mastermind.py
file, you can test it by running a "test
suite", which is a test file that we have prepared for you. The test suite is
called test_mp2.py
and can be downloaded
here.
Download it to the same directory as your mp2_mastermind.py
file.
To run the test suite, open up a terminal, go to the directory containing your code, and type this at the terminal prompt:
$ pytest test_mp2.py
If all is well, it will print some green dots and not report any failures. If there is a test failure, it will report that and indicate which function failed. If that happens, you have some bugs to fix! Note, however, that even if the test script doesn’t report any errors, it doesn’t guarantee that there are none. Test scripts almost never cover 100% of the possible cases, and this one certainly doesn’t. Nevertheless, if your code passes the tests you can at least be more confident that it’s correct.
Coding tips
When writing your functions, try to come up with a simple solution if at all possible. If you write an extremely convoluted/complex solution where a simple solution will work just as well, we will probably take marks off even if your function works correctly. (This is not to say that there is only one solution for these problems, only that we don’t want unnecessary complexity.)
This applies to all parts of this project and all future assignments as well.
Overview: The game of Mastermind
Mastermind is a simple board game for two players. The rules are listed
here, but here is a
quick summary. One player (the "codemaker") picks a secret code consisting of
four code pegs which can be any of six colors (we’ll use the colors red,
green, blue, purple, orange, and white, which we’ll abbreviate as 'R'
, 'G'
,
'B'
, 'P'
, 'O'
, and 'W'
). The order of the pegs is important, so a code
of 'RGBB'
is not the same as a code of 'BRGB'
. Colors can be duplicated in
a code, but they don’t have to be. The codemaker puts the four pegs
representing their code in a secret location where the other player can’t see
them. That player (the "codebreaker") tries to guess the code by laying down
four code pegs that they think might be the correct code. The codemaker
compares the guess with the real code, and puts down zero to four key pegs
which indicate how close the guess is to the solution. For every code peg
which is the correct color in the correct location, a black peg is put down.
For every code peg which is a correct color but not in a correct location, a
white peg is put down. Victory occurs when four black pegs are put down, which
means that all the code pegs in the guess are the same as those in the secret
code. The objective is to guess the code in as few moves as possible.
Here’s an example of a sample game. Let’s say that the secret code was
'ROOB'
(Red, Orange, Orange, Blue). We’ll show the code pegs on the left and
the key pegs on the right (as black ('b'
), white ('w'
), or blank ('-'
)).
Guess Result
----- ------
RGBP bw-- (R is in the correct location, B is not, other colors wrong)
OOWW bw-- (One O is in the right location, one isn't, other colors wrong)
RBOR bbw-
RBBW bw--
RBOO bbww (All colors correct, but two are not in the right locations)
ROOB bbbb (All colors correct and in the right order; victory!)
The order of the key pegs is unimportant, so 'b---'
, for instance, doesn’t
necessarily mean that the first code peg is in the correct location. For
convenience, we put the black key pegs first, then the white ones, then the
dashes.
Your job will be to write a computer program to play Mastermind against you. The computer will be the codemaker and will choose a random code. You will be the codebreaker. The computer has to take your guesses and report how well you are doing. An interaction with the computer will look like this:
New game.
Enter your guess: RGBW
Result: bw--
Enter your guess: OOWW
Result: bw--
Enter your guess: RBOR
Result: bbw-
Enter your guess: RBBW
Result: bw--
Enter your guess: RBOO
Result: bbww
Enter your guess: ROOB
Result: bbbb
Congratulations! You cracked the code in 6 moves!
We will build up to this in stages. All of the code for this miniproject
should be in a file called mp2_mastermind.py
. Don’t forget to write docstrings!
None of these functions need to be very long, so if you find yourself writing a lot of code for any function (say, more than 20 non-docstring lines), you are probably going about the task the wrong way and need to speak to a TA.
0. Designing your own tests
[25]
Before starting the implementation of Mastermind, we ask that you go over the descriptions of make_random_code
, count_exact_matches
, count_letter_matches
, and compare_codes
and write tests for those functions.
You will make your own pytest tests in a seperate file that you will call my_pytest_mp2.py
and submit on Codepost along with mp2_mastermind.py
. Thinking about the desired outputs will help you in designing your functions and save you time when writing code.
We recommend that you take a look at the tests we've provided you as an example. Your file should contain the following:
-
test_make_random_code()
with at least 5 different test cases (Update: We are no longer requiring this since it's a bit trickier to test for randomness, but you can try for an added challenge! Hint: How could you use a loop to test "enough" times that your function eventually generates all possible characters?) -
test_count_exact_matches()
with at least 5 different test cases. -
test_count_letter_matches()
with at least 5 different test cases. -
test_compare_codes()
with at least 5 different test cases.
You are not expected to have more than a simple assert statement for each different test case but make sure to cover a wide array of cases. Ask yourself, what are some edge cases that you may not have considered.
Please do not copy the same test cases that we have provided. Finally, once you have finished the assignment, you should make sure to run your file using pytest
to make sure that your 4 tests pass!
1. make_random_code
[15]
Write a function called make_random_code
which takes no arguments and returns
a string of exactly four characters, each of which should be one of 'R'
,
'G'
, 'B'
, 'P'
, 'O'
, or 'W'
. You may find the random.choice
function very useful for this problem. Do:
>>> help(random.choice)
from inside Python to learn more about it. (Make sure you import
the random
module first!)
2. count_exact_matches
[15]
Write a function called count_exact_matches
which takes two arguments which
are both strings of length 4 (you don’t have to check this). It returns the
number of places where the two strings have the exact same letters at the exact
same locations. So if the two strings are identical, it would return 4; if the
first and third letters of both strings are the same but the other two are
different, it would return 2; and if none of the letters are the same at
corresponding locations it would return 0.
>>> count_exact_matches('RGBP', 'RGBP') 4 >>> count_exact_matches('RGBP', 'GBPR') # letters are not in the same locations 0 >>> count_exact_matches('RRRR', 'OOOO') 0 >>> count_exact_matches('RORP', 'RPOR') # only one letter is in the same location 1 >>> count_exact_matches('RGRP', 'RGRR') # only one letter is _not_ in the same location 3 >>> count_exact_matches('RGBP', 'RGOO') 2 >>> count_exact_matches('RGBP', 'RWBW') 2 >>> count_exact_matches('RGBP', 'WOWO') 0
Use a for
loop, which should iterate over the indices of the string (i.e. the
list [0, 1, 2, 3]
). Recall that you can access letters of a string using the
same syntax you use to access elements of a list (because they’re both
sequences).
3. count_letter_matches
[30]
Write a function called count_letter_matches
which is like
count_exact_matches
except that it doesn’t care about what order the letters
are in; it returns the number of letters of the two strings that are the same
regardless of order.
>>> count_letter_matches('RGBP', 'RGBP') 4 >>> count_letter_matches('RRRR', 'RRRR') 4 >>> count_letter_matches('RRRR', 'ROOO') 1 >>> count_letter_matches('RGBP', 'WOWO') 0 >>> count_letter_matches('RGBP', 'GBPR') # N.B. the order doesn't matter 4 >>> count_letter_matches('RORO', 'OROR') 4 >>> count_letter_matches('ROGO', 'RRGG') 2 >>> count_letter_matches('RGBP', 'OROG') 2 >>> count_letter_matches('RGBP', 'BWBR') 2 >>> count_letter_matches('RBBP', 'GRBO') 2
This function is a bit tricky, so here are some hints. Of course, you don’t have to follow our hints; you can solve this problem any way you like as long as the solution isn’t ridiculously long or convoluted.
-
Convert one of the two strings into a list using the
list
built-in function before doing anything else (think about why only one is needed!). -
Go through one sequence (remember that strings, lists, and
range
are all Python sequences) character by character using afor
loop. For each character that is in the other sequence, increment a counter variable and then remove the character from the list (not the sequence you are iterating over) using theremove
method on lists. Use the form<x> in <seq>
(see section A) to find out if a character is in a sequence. -
Once you have gone through, the counter variable is the answer.
4. compare_codes
[30]
Write a function called compare_codes
which takes two arguments which are
both strings of length 4. The first argument is called code
and will
represent the secret code chosen by the codemaker. The second argument is
called guess
and will represent the guess of the codebreaker. The function
will output a string of length four consisting only of the characters 'b'
,
'w'
, and '-'
(for black, white, and blank key pegs). Note that the
'-'
character must be the hyphen/dash character, not the '_'
underline
character. This 4-character string represents the
key pegs i.e. the evaluation of the guess (so 'bbbb'
would be a perfect
guess, 'wwww'
would mean all colors are correct but in the wrong order, and
'----'
would mean that no colors are correct).
This function is the heart of the game. Fortunately, if you’ve written the two previous functions correctly, writing this one is a piece of cake. Here is the algorithm (solution method) you should use:
-
The count of black pegs can be obtained just by calling the function
count_exact_matches
. -
The count of white pegs can be obtained by calling both the functions
count_letter_matches
andcount_exact_matches
and subtracting the result of the second function call from the first. (Note that since you’ve already calledcount_exact_matches
on the two strings, you shouldn’t call it a second time.) -
The count of blank pegs is the difference between 4 and the sum of the count of black and white pegs.
You will need to take these three counts and create a string for the result.
Make sure that the string includes the 'b'
s first, then the 'w'
s, then
the '-'
s.
Note that this function doesn’t print anything; it just returns a string.
>>> compare_codes('RGBP', 'RGBP') 'bbbb' >>> compare_codes('RGBP', 'WOWO') '----' >>> compare_codes('RGBP', 'PBGR') 'wwww' >>> compare_codes('ROBO', 'RWBW') 'bb--' >>> compare_codes('RBGP', 'RGBP') 'bbww'
5. run_game
[30]
Now we’re ready to write the function that actually runs the game. It will be
called run_game
, and it will take no arguments. When called, this function
will do the following:
-
Print
New game.
. -
Select a secret code using the
make_random_code
function you defined above, and store this in a variable. -
Prompt the user for a guess using the
input
function and the prompt stringEnter your guess:
. -
Evaluate how well the guess matches the stored code using the
compare_codes
function and print out the result in the formatResult: <result string>
. -
If the result string is
bbbb
, then the function will printCongratulations! You cracked the code in <N> moves!
and exit (where<N>
is the number of moves since the game began). Otherwise, the function will go back to step 3 above.
In other words, the interaction with the user should be the same as what was described above at the beginning of this section. Here are a few hints:
-
Every time the function asks for a guess, a counter which holds the number of moves should be incremented.
-
Consider using an infinite loop (with a
while
statement) and abreak
statement inside the loop once the exit condition applies. -
Make sure your output and
input
prompts match that as described; make sure to test your program! -
While testing, we recommend fixing 1-2 randomly-generated codes when you run the program to see if you
get the expected output/guess counts. Try starting with the code of
'ROOB'
following the interaction shown above to check that you get the expected results until a game is one, then try a few other test cases (e.g.'RORO'
,'RRRR'
,'ROPB'
). Make sure to change this fixed string to the result ofmake_random_code
before submitting!
6. Using the debugger
[30]
For this part of the assignment, you will use the debugger to step through the execution of your Mastermind game code. You will focus on the interaction between compare_codes
and its helper functions,
count_exact_matches
and count_letter_matches
. This will help you understand how these functions work together to evaluate a guess in the game. If you are not familiar enough with the debugger,
you can take a look at the walkthrough guide posted on the website under resourses. Then:
-
Place a breakpoint at the beginning of
compare_codes
. -
Place additional breakpoints at the beginning of the
count_exact_matches
andcount_letter_matches
. -
Start the debugger session by executing your Mastermind game code.
-
Use the "Step Into" feature to move through the code line by line.
Pay special attention to the how the execution moves from function to function and how the data is passed into them. Note how the variables change and the conditions under which helper functions are called and how
their return values affect the execution of compare_code
. Consider how understanding the flow between functions and their helpers can help in debugging and how you might use this strategy in the future
to diagnose and fix bugs. In addition to your code, submit 1 to 2 paragraphs in a docstring at the bottom of MP2_mastermind.py
on your observations and reflection. You should explain the flow between
the different functions we highlighted when running Mastermind. Make sure to include any questions you may have.
Final Reminders
Here are a few final reminders to check before submitting your assignment:
- Make sure you are running the provided
test_mp2.py
test suite as described above. For full credit, your program should not produce any errors. If any errors are reported, you have some debugging to do. - Double-check your code for proper docstrings following the material in Lecture 3 and the CS 1 Code Quality Guide.
- Make sure you are following Python language conventions taught in class/the CS 1 Code Quality Guide. Some common conventions
students have misused in the past for this assignment include:
- Not using lowercase_snake_casing for function and variable names (no upper-case characters!)
- Make sure you are using spaces, not tabs (refer to Lecture 3 video where El answered a student question/showed how to configure VSCode settings if needed)
- Make sure you don't have any unused/unnecessary variables in functions
- No variables should be outside the scope of a function
- When using loops, make sure you are not using them unnecessarily
- Do not use any advanced Python features that we have not taught; the reason for this
requirement is 1. students are not expected to have prior experience and 2. students who do have some experience
have often misused advanced Python without understanding trade-offs where simpler implementations are more appropriate.
Advanced features include list comprehension, any modules that are not
random
, functional Python (e.g.map
), etc. If you want to discuss these trade-offs/ask about exceptions, you should reach out to El (not TAs); they are happy to talk about this!
- And finally, double-check this spec once more to make sure you've followed any missed requirements!
pip install pytest
, but pip3
is safer in case you have Python 2 installed on your computer alongside Python 3. pip3
will only add packages to Python 3. If for some reason pip3
doesn’t work, try just pip
, and if neither works, ask a TA for help.