The model of the game

When we develop a piece of code with an object-oriented mindset, we try to model the real world and map real-world objects to objects in the program. You certainly have heard of object orientation explained with the very typical examples of geometric objects, or the car and the motor thing to explain composition. Personally, I believe that these examples are too simple to get a good understanding. They may be good for starters, but we are already in the fourth chapter of the book. The Mastermind game is much better. It is a bit more complex than just rectangles and triangles, but not as complex as a telecom billing application or an atomic power plant control.

What are the real-world objects that we have in that game? We have a table and we have pins of different colors. There are two Java classes that we certainly will need. What is in a table? There are rows each having four positions. Perhaps we will need a class for a row. A table will have rows. We will also need something that hides the secret. This also may be a row and each row may also hold the information about how many positions and how many colors are matching. In case of the secret row, this information is obvious: 4 and 0.

What is a pin? Each pin has a color and generally, that is it. There are no other features of a pin, except that it can be inserted into a hole on the table, but this is a real life feature we will not model. Essentially, a pin is a color and nothing else. This way, we can eliminate the pin class from our model early on, even before we created it in Java. Instead, we have colors.

What is a color? This is something that may be hard to immerse into the first time. We all know well what a color is. It is a mixture of different frequency of lights, as our eyes perceive it. We can have paints and prints in different colors, and so on. There are very many things that we do not model in this program. It is really hard to tell what we model about color in our code because these features are so obvious that we take it for granted in real life; we can tell about two colors that they are different. This is the only feature we need. To do this, the simplest class of Java can be used:

package packt.java9.by.example.mastermind; 
public class Color {}

If you have two variables of the type Color, you can tell if they are the same or not. You can use object identity comparing a and b using the expression a == b or you can use the equals method inherited from the Object class, a.equals(b). It is tempting to encode the colors with letters, or use String constants to denote them. It may be easier first, but there are serious drawbacks later. When the code becomes complex, it leads to bugs; it will be easy to pass something also encoded as String instead of a color and only unit tests may save the day. Better, the compiler already complains in the IDE when you type the wrong argument.

When we play the game, the pins are in small boxes. We pull pins out of the boxes. How do we get the colors in the program? We need something from where we can fetch colors or looking at the other way something that can give us colors. We will call it ColorManager. ColorManager knows how many different colors we have and any time we need a color, we can ask for it.

Again, there is a temptation to design the ColorManager that it can serve a color by its serial number. If we have four colors, we could ask for color number 0, 1, 2, or 3. But then again, it would just implicitly encode the colors as integer numbers, which we agreed we will not. We should find the minimum feature that we will need to model the game.

To describe the structure of the classes, professional developers usually use UML class diagrams. UML is a diagram notation that is standardized and is almost exclusively used to visualize software architecture. There are many diagram types in UML to describe the static structure and the dynamic behavior of a program. This time, we will look at a very simplified class diagram.

We have no room to get into the details of UML class diagrams. Rectangles denote the classes, normal arrows denote the relations when a class has field of the other class type, and triangle headed arrow means that a class extends another. The arrow points to the direction of the class being extended.

A Game contains a secret Row and a Table. The Table has a ColorManager and a List<> of Row. The ColorManager has a first color and has a Map<> of Color. We have not discussed why that is the design, we will get there and the diagram helps us walking that road. A Row is essentially an array of Color.

The one who plays the game has one function: it has to guess many times until it finds the hidden secret. To get to the model of the ColorManager, we will have to design the algorithm of the Guesser.

When the player makes the first guess, any combination of colors is just as good as any other. Later, the guesses should consider the responses that were given for previous guesses. It is a reasonable approach to try only color variations that can be the actual secret. The player selects a variation and looks at all previous guesses assuming that the selected variation is the secret. If the responses to the rows he has already made are the same for this variation as for the unknown secret in the game, then it is reasonable to try this variation. If there is any difference in the responses, then this variation is certainly not the variation that was hidden.

To follow this approach, the guesser has to generate all possible color variations one after the other and compare it against the table. The guesser code will not create and store all the possible variations ahead, but it has to know where it was and has to be able to calculate the next variation that comes. This assumes an order of the variations. For a short while, let's forget that no color may appear twice in a variation. A simple ordering can be made the same way as we sort decimal numbers. If we have a three-digit number, then the first one is 000, the next one is 001, and so on until 009, always fetching the next digit for the last position. After that, 010 comes. We increased a digit next to the last one and we set the last one to 0 again. Now, we have 011, 012, and so on. You know, how we count numbers. Now, replace the digits with colors and we have only six and not ten. Or, we have as many as we want when we instantiate a ColorManager object.

This leads to the functionality of the ColorManager. It has to do the following two things:

  • Give the first color to the caller
  • Give the next color that follows a given color (we will name the method nextColor)

The latter functionality should also signal some way when there is no next color. This will be implemented using another method, named thereIsNextColor.

It is a convention to start the method names that return a Boolean value with is. That would lead to the name following this convention isThereNextColor, or isNextColor. Either of these names explains the functionality of the method. If I ask the question isThereNextColor, the method will answer me true or false. But, this is not how we will use the method. We will talk in simple sentences. We will use short sentences. We will avoid unnecessary, gibberish expressions. We will also program that way. Most probably, the caller will use this method in an if statement. They will write the following:
If( thereIsNextColor(currentColor)){...}
and not
if( isThereNextColor(currentColor)){...}
I think the first version is more readable and readability comes first. Last, but not least, nobody will blame you if you follow the old convention, and in case that is the company standard, you have to anyway.

To do these, the ColorManager also has to create the color objects and should store them in a structure that helps the operations being performed.

package packt.java9.by.example.mastermind; 

import java.util.HashMap;
import java.util.Map;

public class ColorManager {
final protected int nrColors;
final protected Map<Color, Color> successor = new HashMap<>();
final private Color first;

public ColorManager(int nrColors) {
this.nrColors = nrColors;
first = new Color();
Color previousColor = first;

for (int i = 1; i < nrColors; i++) {
final Color thisColor = new Color();
successor.put(previousColor, thisColor);
previousColor = thisColor;
}
successor.put(previousColor, Color.none);
}

public Color firstColor() {
return first;
}

boolean thereIsNextColor(Color color) {
return successor.get(color) != Color.none;
}

public Color nextColor(Color color) {
return successor.get(color);
}
}

The structure we use is a Map. Map is an interface defined in the Java runtime and is available since the very early releases of Java. A Map has keys and value, and for any key, you can easily retrieve the value assigned to the key.

You can see on the line, where the variable successor is defined that we define the type of the variable as an interface, but the value is an instance of a class. Obviously, the value cannot be an instance of an interface because such beasts do not exist. But, why do we define the variable to be an interface? The reason is abstraction and coding practice. If we need to change the implementation we use for some reason, the variable type still may remain the same and there is no need to change the code elsewhere. It is also a good practice to declare the variable to be an interface so that we will not have the temptation to use some special API of the implementation that is not available in the interface just by convenience. When it is really needed, we can change the type of the variable and use the special API. After all, there is a reason that API is there, but the mere temptation to use some special thing just because it is there is hindered. This helps to write simpler and cleaner program.

Map is only one of the interfaces defined in the Java runtime belonging to the Java collections. There are many other interfaces and classes. Although, the JDK and all the classes are a vast amount and almost nobody knows all the classes that are there, collections is a special area that a professional developer should be knowledgeable about. Before getting into details on why HashMap is used in this code, we will have an overview of the collection classes and interfaces. This will help us also understand the other collections used in this program.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.128.204.5