Introduction
作业要求实现一个 Yahtzee
,即快艇骰子游戏。这个游戏需要了解游戏规则,考察的知识点是继承的运用。
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
Summary of tasks
You will implement the following classes:
YahtzeeGame
Hand
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.
Overview
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.
Hands
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.
Scoring categories
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”?