For this assignment you will implement a number of classes for an implementation of a dice game based on the game Yahtzee. The purposes of this assignment are:
- To use interfaces and inheritance in a realistic way
- To give you a chance to make some design decisions related to inheritance
- To give you more practice using arrays and ArrayLists
You will implement the following classes:
plus, at a minimum, the following eight classes, all of which directly or indirectly implement the Category interface:
AllButOneOfAKind AllButTwoOfAKind AllOfAKind Chance CountOccurrences FullHouse LargeStraight SmallStraight
All of your code goes in package hw3 . In addition to the classes listed above, you will implement whatever additional classes you decide are necessary in order to exploit inheritance to facilitate code reuse. See the discussion of scoring categories below for more information.
The exact definition of each of the eight category classes listed above can be found by reading the class javadoc.
Yahtzee is normally played with five six-sided dice and a scorecard. One round consists of rolling the dice up to three times, and then filling one of the categories on the scorecard with a score. The score depends on both the criteria for the category and the actual dice values, and the player will normally choose an unfilled category so as to maximize the resulting score.
In our version of the game, the number of dice, the maximum value on the dice (i.e., the number of faces), the max number of rolls per round, and the types of categories on the scorecard, will all be configurable.
If you are not at all familiar with the game, don’t worry, it is not too complicated. Take a look at the Wikipedia page for an overview, https://en.wikipedia.org/wiki/Yahtzee
Note that our version of the game does not include several features described on Wikipedia or in the traditional versions of the game. In particular, we do not distinguish the “upper section” and “lower section” categories, there is no bonus for the “upper section”, there is no special bonus for a second Yahtzee, and there are no jokers.
The two key abstractions in the design are hands and scoring categories.
A hand, represented by the Hand class that you will implement, is a basically a list of integers representing the current states of all the dice (in the traditional game there would be 5 numbers with possible values 1 through 6). However, the dice are partitioned into two lists: available dice and fixed dice. Initially, all dice are available. When the dice are “rolled”, random values in the appropriate range are generated for the available dice only; the fixed values are not modified. If the maximum number of rolls has not yet been reached, the player can choose to “keep” some of the current available dice values, which means they are moved to the fixed list so they won’t be modified by the next roll. Likewise, the player can choose to “free” any of the fixed values, so they will be re-rolled. Note that in this design, an individual die isn’t represented by a special type of object; a die value is just an integer. All methods that return arrays containing dice values must return the values in ascending order.
After the maximum number of rolls is reached, all dice are automatically moved to the fixed list and the hand can no longer be modified. A new hand must be created for the next round.
See the javadoc for details. Also note that the client normally obtains a new Hand using the method createNewHand in the YahtzeeGame class, not by calling the constructor directly. (You can see how this works by reading at the doOneTurn() method of the sample UI.)
Hand example. Suppose we display a dice group as a string by listing first the available dice and then the fixed dice in parentheses. For example, in a game with 5 dice, after the first roll we might see the values such as this:
2 3 3 4 6 ()
Depending on which scoring categories you need to fill, you might decide to keep 2, 3, and 4 (perhaps in the hope of completing a straight). Now you have
3 6 (2 3 4)
On the next roll, the 6 and the other 3 are then replaced by random values, but the 2, 3, and 4 you selected remain fixed. If (for example) you now roll a 2 and a 5, you would have
2 5 (2 3 4)
At this point you could choose to keep the 5 (to make a small straight, maybe hoping the next roll will give you a large straight). Now you have
2 (2 3 4 5)
In the traditional game, you get a maximum of three rolls; in that case if you (for example) rolled a 4, you’d end up with
(2 3 4 4 5)
That is, when you reach the maximum number of rolls, all dice are automatically fixed. Once all dice are fixed, we say the hand is complete. At this point in the game, the player chooses one of the scoring categories and uses the completed hand to fill that category.
A scoring category represents one row of the score sheet. A category object stores the actual score for that category along with the hand that was used to fill the category. A given category object also contains the algorithms needed to a) determine whether a given hand satisfies the criteria defined for the category (e.g., is it a straight, or three-of-a-kind, or whatever), and b) determine what the potential score would be for a given hand, if it were used to fill that category.
There are many different possible categories, each with its own particular algorithms. For example, the traditional game has a three-of-a-kind category: a hand satisfies the category if it contains any three numbers that are the same, and it is scored by summing the values of all the dice. The traditional game also has a “large straight” category: a hand satisfies the category if it has 5 consecutive values, and it always receives a fixed score of 40.
This is where polymorphism becomes useful. The client using this code (e.g., think of the client as the text-based UI provided in the sample code) does not care about the details of what the categories are or how each category calculates its score. The client just needs to be able to invoke methods on a category to find out whether a given dice group satisfies it, what the score would be, and to inform the category when it has been selected to be filled by a given hand.
Therefore, a scoring category is defined by an interface, called Category. This interface is already written and you should not modify it. See the javadoc for detailed descriptions of the methods. See the text-based UI to see how it is used from the client’s point of view. In particular, you can see in the printCategories method where the UI just iterates over the categories and displays the potential score or actual score from each one.
There are eight concrete subtypes of the Category interface that you are required to implement in package hw3 , as listed above. However, a class need not implement the interface directly: it could instead extend some other class that implements Category. If you just add the declaration implements Category to each of these classes and then write all the required methods, you would find yourself writing much of the same code over and over again. Even though there areseveral different algorithms involved in the different classes (e.g. three-of-a-kind vs. a large straight), there is also a lot in common between the classes. You should carefully think about how to design an inheritance hierarchy so that you can minimize duplicated code between the classes. You might think about starting with an abstract class containing features that are common to all category types, such as isFilled() or getHand() . There are additional opportunities for sharing code to think about too. Are there code similarities between “large straight” and “small straight” that you can exploit? How about between “all the same kind” and “all but one the same kind”?