Assignment 5: Mastermind
Goals: Design the Mastermind game.
Instructions
This assignment is long. Start early.
Make sure you follow the style guidelines that we enforce. For now the most important ones are: using spaces instead of tabs, indenting by 4 characters, following the naming conventions (data type names start with a capital letter, names of fields and methods start with a lower case letter), and having spaces before curly braces.
You will submit this assignment by the deadline using the online submission system. You may submit as many times as you wish. Be aware of the fact that close to the deadline the system may slow down to handle many submissions - so try to finish early.
There will be a separate submission for each problem - it makes it easier to grade each problem, and to provide you with feedback for each problem you work on.
The submissions will be organized as follows:
Homework 5: Your game implementation
The game: Thursday October 17th, 9:00pm
Practice Problems
Work out these problems on your own. Save them in an electronic portfolio, so you can show them to your instructor, review them before the exam, use them as a reference when working on the homework assignments.
Problem 20.2 on page 296
Problem 20.8 on page 306
Problem 20.9 on page 306
Problem 21.1 on page 312
Problem 21.3 on page 312
Problem 21.4 on page 315
Problem 21.5 on page 320
Problem 21.6 on page 320
Problem 21.7 on page 320
5.1 Mastermind
You are going to design the classic code-cracking game Mastermind.
In Mastermind, a code is a sequence (list) of colors. The player enters what they think the sequence is, and then the game gives the player feedback on how accurate their guess is (more on this below). They then use this information to make a new, more informed guess, and the process repeats. The game ends when the player enters the correct sequence or they run out of guesses (a win/loss, respectively), and the correct sequence is revealed to the player.
Note that the correct sequence is randomly generated for every new game.
Your game will use the javalib.funworld library, which provides an implementation of Worlds and bigBang similar to what was used in Fundies 1. Read the documentation carefully for more information.
5.1.1 The Feedback
The feedback for a given guess is given in two numbers: exact matches and inexact matches. For example if the correct sequence is:
and the player enters:
the player would be told they got two exact matches (for the first and fourth entries in the guess), and two inexact matches (for the second and third entries in the guess).
Exact matches are only counted for colors that appear in the exact same place in the guess and the correct sequence.
The entries in the guess sequence that are part of the exact matches are never counted towards the inexact matches.
Every entry in the correct sequence can only be used once for an inexact match.
Be sure you can work through and understand this example. Draw appropriate lines between the correct and guessed sequence to see why the last two blues in the guess don’t count toward the inexact matches, and why the red and green at the end of the correct sequence don’t count toward the inexact matches.
5.1.2 Configurability, Data Constraints and Assumptions
Whether or not duplicate entries are allowed in the correct sequence (in the example given above, they are)
The length of the sequence to be guessed (in the example given above, 6)
The number of guesses a player is allowed
A list of colors that the sequence could have been made from, which also serves as the colors a player can guess
One of your constructors should take in exactly these four parameters, and your program should be designed such that this data is all a player (or grader) would have to change to reconfigure their gameplay experience.
The length of the sequence to be guessed is 0 or less
The number of guesses a player is allowed is 0 or less
The length of the list of colors is 0
Duplicates are disallowed and the length of the sequence to be guessed is greater than the length of the list of colors
The colors in the list of colors are unique
The list of colors will never be longer than 9 entries
5.1.3 User Interaction
The user uses three kinds of keys to interact with the game: numbers, enter, and backspace.
The player uses numbers to build up a guess for the sequence, with each number corresponding to one color. For example, if the list of colors the player could choose from is red, blue and green, 1 would correspond to red, 2 to blue, and 3 to green. Note that once a player has entered a sufficient number of colors for their current guess, additional number keystrokes should be ignored, as should numbers bigger than the number of colors than there are to guess.
Once a player has entered as many colors for their current guess as the length of the sequence they are guessing is, the player submits their guess with the enter key. The game then reveals the number of exact and inexact matches they got for that guess. If the guess was correct or the player has run out of guesses, the game then ends.
If a player is in the middle of entering a guess and wants to change it, they should be able to use backspace to clear the last entry they made in their unfinished guess (if a color has been entered at all).
Any other keystrokes should be ignored.
5.1.4 Game Visualization
When the game is launched, the player should see the colors they have to choose from at the bottom of the screen, the hidden, correct sequence at the top, and a grid for guesses that will be filled.
For example, in a game where the colors are cyan, pink, blue, and green, there are 10 guesses to be made, and the sequence size is 3, the game might look something like this:
In this example, there are more colors to guess than there are possible colors in the sequence: this won’t always be the case, and your game should be able to support any possible configuration within the data constraints and assumptions outlined above.
Guesses should be filled from the bottom-right up to the top-left.
Continuing the example above, after entering 1 and 4, for cyan and green, the game would look like this:
Backspaces should clear the latest color the player has entered (so long as they have not submitted their guess and received feedback).
Continuing the example above, after pressing backspace and then 3 and 2, for blue and pink, the game would look like this:
After pressing enter on a full guess, the player should be shown how many exact matches and how many inexact matches they received next to the guess that was just entered.
For example:
The game continues in this fashion. Halfway through the game, it might look like this:
After the player enters the correct guess, they should be given a congratulatory note that does not interfere visually with other gameplay elements, and the sequence should be revealed at the top:
If the player runs out of guesses and never guesses correctly, they should be rebuked with a note that does not interfere visually with other gameplay elements, and the sequence should be revealed at the top:
Stop!
Now that you have a proper visual idea of the game, we recommend going back and re-reading the entire assignment up to this point. The rest of the writeup deals with technical matters which, while important, won’t be of much help if you don’t fully understand how the game should work.
5.1.5 Design Hint
A finished guess and unfinished guess each have a sequence of colors and each must be able to be drawn, but otherwise must relay different information and support different behavior.
The player is always interacting with exactly one unfinished guess, and must be able to view a list of finished guesses.
5.1.6 Local Variables
You will almost certainly want to use local variables for this assignment.
Be cautious, because some programmers use local variables as an excuse for not using helper methods. Remember, one task per method.
Let’s say we were computing the average of a list, and returning 0 if the list was empty:
public int average() { if (this.length() == 0) { return 0; } else { return this.sum() / this.length(); } }
The problem, of course, is the length is being computed twice. One way to get around this would be to use a helper method:
public int average() { return this.averageHelper(this.length()); } public int averageHelper(int length) { if (length == 0) { return 0; } else { return this.sum() / length; } }
Why did the programmer choose to compute the sum in the helper and not in the outer method and pass it to the helper?
This is fairly verbose for such a simple operation, however. Instead, we can use a local variable:
public int average() { int length = this.length(); if (length == 0) { return 0; } else { return this.sum() / length; } }
This local variable could have been called something besides length.
As in ISL, local variables should be used to avoid duplicate computation, clarify what expressions mean, and contain simple computations another method wouldn’t need to use. Proper usage of local variables, also like in ISL, is a judgement call.
5.1.7 Getters, instanceof, Casting, Exceptions, etc.
As we have explained many times in lecture and lab, reliance on getters, instanceof, casting, fields of fields, and fields of parameters of another class are all poor design. We have none of these in our implementation, so we know that it is possible to design the game without these. Please do not ask us whether or not you are allowed to use these in some cases or how many points you will get off if you do: the answers are no and some, respectively.
Sometimes, to get around the use of instanceof, a poorly designed program will check whether or not the length of a list is 0, which is effectively asking whether or not a list is an instance of an empty list. The only time you should do this is in your constructor when checking for data constraints.
Sometimes, to add methods to one case of union data but leave others unsupported (for example, getFirst on a list), a poorly designed program will throw an exception in one case of the data. This practice will invariably lead to exceptions at runtime, and is always an indication of bad design. The only one of our methods that throws an exception is our Color getIndex(int index) method on ILoColor, which will be triggered when the given index is equal to or longer than the length of the list, which is standard programming practice. The only time we use this method, though, is when a user selects a color and when we randomly generate the sequence at the beginning of the game. You should also only use an index-based method in these cases, and this is the only kind of exception we expect your code to throw outside of a constructor (which should never occur during gameplay).
5.1.8 Some Java
While somewhat hacky, the easiest way to tell if a keystroke is between 1 and 9 is to see if "123456789" contains it.
To convert a numeric string s to an integer, use Integer.valueOf(s). Beware this will throw an exception if s is not the string of an integer.
To tell if two Colors are equal, use the equals method, like we do for Strings.
5.1.9 A Note About Randomness
There are two ways to generate random numbers in Java. The easiest is to use Math.random(), which generates a double between 0 (inclusive) and 1 (exclusive). You can multiply this number by some integer to make it bigger, then coerce to an int to produce a random integer in the range you wish. However, this is not easily testable: you’ll get different random values every time.
The better way to generate random numbers is: First, import java.util.Random at the top of your file. Next, create a new Random() object, and use its nextInt(int maxVal) method, which will give you a random integer between zero (inclusive) and maxVal (exclusive).
This is known as a "pseudorandom number generator", since the numbers aren’t really random if they can be reliably repeated...
One of them, to be used for testing, should take in a Random object whose seed value you specify. This way your game will be utterly predictable every single time you test it.
The second constructor should not take in a Random object, but should call the other constructor, and pass along a really random object:
import java.util.Random; class YourWorld { Random rand // The constructor for use in "real" games YourWorld() { this(new Random()); } // The constructor for use in testing, with a specified Random object YourWorld(Random rand) { this.rand = rand; ... } } Now, your tests can be predictable while your game can still be random, and the rest of your code doesn’t need to change at all.