Wednesday, July 17, 2013

More about Scorer

The first thing I did in my Scorer class was create an enumerated data type for all the categories of scoring.  I made it public, but I'm thinking that it could be private.  I'll hold off on changing it.  Here it is:

public enum Category { ONES, TWOS, THREES, FOURS, FIVES, SIXES,
TKIND, FKIND, HOUSE, LOW, HIGH, YAHTZEE, CHANCE }

This came in very handy in several ways.  In my tally method, which is where all the mess of tallying up a score was done, I had a big switch statement.  I passed a DiceTray and a Category to the tally method and I switched on the Category.  Inside the switch my cases were things like:

switch (cat)
{
    case ONES:
        ...
    case TWOS:
        ...

etc.

Another way that the enumerated type came in handy is that the ordinal values of the various categories could be used as indexes into arrays.  I had a boolean array that I called taken where I kept track of whether or not a given category had been used yet.  I could say something like this:

case TKIND:
    if (!taken[Category.TKIND.ordinal()])
    {
       ...

Now let's talk about how the scoring was done.  First let's talk about scoring the ones through sixes, all of which require the same algorithm.  I made a helper (private) method called scoreUpper to avoid having a lot of duplication in the first six cases.  I called this method from the tally method like this, where t was the DiceTray that got passed to tally:
switch(cat)
{
    case ONES:
        scoreUpper(t, Category.ONES);
        break;
    case TWOS:
        scoreUpper(t, Category.TWOS);
        break;
    case THREES:
        scoreUpper(t, Category.THREES);
        break;
    case FOURS:
        scoreUpper(t, Category.FOURS);
        break;
    case FIVES:
        scoreUpper(t, Category.FIVES);
        break;
    case SIXES:
        scoreUpper(t, Category.SIXES);
        break;

If a player chooses to score a DiceTray as one of these upper values, you have to add up the occurrences of that value in the tray.  For example, if your dice values are 3, 4, 5, 3, 3 and the player says to score it as Threes, then the value of that round will be the sum of three 3's or 9. Here is how the algorithm for scoreUpper works:

  • Set a local variable, value, to 0.
  • If the category has not been taken already,
    • walk through the dice tray (t) and if the value of a die is of the category being used (cat), then add it to value. Here is how I did that.  Keep in mind that the ordinal value of ONES is zero, but when I'm counting ones I want to add up 1's.  The ordinal value of the first six categories is one less than the face value of that category.  So I have to add 1 to the ordinal value.

      for (int i = 0; i < 5; i++)
          if (t.getValue(i) == cat.ordinal()+1)
              value += cat.ordinal()+1;
      
      
    • Put that value into the scores array at the location of the category being used. (scores[cat.ordinal()] = value)
    • Set the location in the taken array (the one that corresponds to the category) to true.
    • Add the value onto the subtotal field.
    • If the subtotal is 63 or more and the bonus has not yet been taken, then set the bonus to  35.
    • Add value and bonus to total.
I didn't make any other helper methods.  I just handled the other cases right in the tally method.

I found it useful to have a local integer array in the tally method that I called count.  I made it be of size 7 but I never used the 0 cell.  I used cells 1 through 6 to correspond to the values of dice.  I started all the cells of count at 0.  I used this count array in almost every case.  The first thing I did in almost every one of my cases was to go through the dice tray and add 1 to the count of each possible dice value.  If the dice in the tray were 3, 4, 1, 4, 5, then after I looked through the tray my count array would have the values in the table below (the top row shows the indexes of the cells of the array and the bottom row shows the values in the array).  There is 1 one, no twos, 1 three, 2 fours, 1 five, and no sixes.  I don't use the 0 cell.
0123456
0101210

Doing that count is easy:

for (int i = 0; i < 5; ++i)
    count[t.getValue(i)]++;

One you have the counts of the various dice, you can check to see if one of the counts is 3 (good for three of a kind) or 4 (good for four of a kind) or 5 (yahtzee!)  For a full house you can check to see if one count is 2 and another is 3.

So my Scorer class is finished now.  I am going to work on Player and then put it all together somehow.  I expect I'm going to have a Yahtzee class that will contain the main.  I'll write more when I get finished.

1 comment:

  1. Calculating scores was handled in my Game class. I figure the Game itself is 'aware' of the actual rules. I read a choice(1-13) from the user and switched on that value to call the appropriate scoring method. I created a single method for the Ones-Sixes that took the number as the parameter. I used the tally method of the DiceTray and returned a score that matched the face value with it's count.

    For Three/Four of a Kind I wrote separate methods that check for those conditions, but the score is the same, all dice, so I used the sum method to get that score.

    Chance is all dice as well, but there's nothing to check, you just get the sum and pass it on.

    The remaining categories have their own methods and return a particular score. I coded these scores as constants declared with my fields.

    If the dice don't match the category, the methods return a score of 0.

    So when the scoreRoll method is called, it gets the player's choice(a separate method), and runs through the switch statement, executing the appropriate score calculation method. It then returns the category and the score.

    "Wait...it returns two things? You can't do that!"

    Actually, you can, by returning an array. I store the category in element 0 and the score in element 1 and send this to the Player to be sent to the Score Sheet.(actually my final version of the method returns three things, the third having to do with the Yahtzee Bonus/Joker rules).

    Coding the different score calculations is really fun and good practice with control and decision structures.

    An easy way to test your score calculations is to hard code particular dice rolls. An extra method or two in the Die or DiceTray class can help you with this. When I was coding the Yahtzee Bonus/Joker rules, I created a method that sets all the dice to the same value instead of randomizing them. Don't forget to set things back to normal when you're done.

    ReplyDelete