CMPS 134 Fall 2023
Final Exam Dice Game: Sample Solution

Introduction

What follows is a sample solution to one of the problems on the CMPS 134 final exam. But it is more than a sample solution in that it attempts to capture the thinking/reasoning process that a programmer might go through in developing that solution.

It is offered here in the hope that it will be read and understood by interested students, especially those who may have struggled in the course due to a lack of problem-solving acuity (and who are, either by choice or curriculum requirements, intending to enroll in CMPS 144 next semester).

The Problem

Consider this rather uninteresting game played with a pair of six-sided dice, call them Die A and Die B. As the game progresses, let sum(A) (respectively, sum(B)) be the sum of the results of the rolls of Die A (respectively, Die B). Let Delta = sum(A) - sum(B). We call this the game's Delta-value.

The game begins by rolling Die A, which establishes a positive Delta-value. Thereafter, on each turn, you roll either Die A or Die B, respectively, according to whether the game's Delta-value is negative or positive. The game ends when the Delta-value becomes zero. The goal of the game is to achieve this after as few dice rolls as possible. (Or perhaps the goal should be to make as many rolls as possible. It's a matter of interpretation!)

Below is an incomplete Java method that is intended to "play" this game using two given instances of the SixSidedDie class. Your task is to supply the method's body. The only two methods you need from the SixSidedDie class are these:

// Reports the result of this die's most recent roll.
public int numPips() { ... }

// Rolls this die.
public void roll() { ... }

Here is the method to complete:

/* Plays the game described above using the two given six-sided dice. The
** value returned is the number of dice rolls that were made during the game.
*/
public static int diceGame(SixSidedDie dieA, SixSidedDie dieB) {

   < method body missing here >

}

A Solution, Explained

The first observation to be made is that the two dice that are to be involved in the game are provided (by the hypothetical caller) via the method's two formal parameters. Hence (contrary to answers given by several students), no new SixSidedDie objects ought to be created inside the method.

Among the other observations to be made is that the method (seemingly) needs to keep track of at least three pieces of data, one being how many dice rolls have occurred and the others being sum(A) and sum(B). After all, the first is the value to be returned upon completion of the game and the latter two are used in computing the game's Delta-value, which determines when the game ends.

As the problem says, the game involves a sequence of turns, during each of which one of the two dice is rolled. Furthermore, the game ends when its Delta-value (i.e., sum(A) - sum(B)) becomes zero. This clearly and strongly suggests the use of a while-loop (whose loop guard corresponds to the condition sum(A) - sum(B) ≠ 0) each iteration of which performs one turn of the game.

The observations made above allow us to devise this skeleton of a solution (in which text shown in red is pseudocode):

/* Plays the game described above using the two given six-sided dice. The
** value returned is the number of dice rolls that were made during the game.
*/
public static int diceGame(SixSidedDie dieA, SixSidedDie dieB) {

   int rollCount = 0; // # of dice rolls to have occurred
   int sumA = 0;      // sum(A)
   int sumB = 0;      // sum(B)

   prepare for loop

   while (sumA - sumB != 0) {

      roll the appropriate die
      update either sumA or sumB

      rollCount = rollCount + 1;
   }
   return rollCount;
}

As the game's description says, the decision as to which die is to be rolled during a given turn is based upon the game's Delta-value, with Die A to be rolled when it is negative and Die B to be rolled when it is positive. Modifying our code accordingly, we get this (with the refinement in blue):

/* Plays the game described above using the two given six-sided dice. The
** value returned is the number of dice rolls that were made during the game.
*/
public static int diceGame(SixSidedDie dieA, SixSidedDie dieB) {

   int rollCount = 0; // # of dice rolls to have occurred
   int sumA = 0;      // sum(A)
   int sumB = 0;      // sum(B)

   prepare for loop

   while (sumA - sumB != 0) {

      if (sumA - sumB < 0) {  // roll dieA if Delta is negative
         dieA.roll(); sumA = sumA + dieA.numPips();
      }
      else {  // roll dieB if Delta is positive
         dieB.roll(); sumB = sumB + dieB.numPips();
      } 

      rollCount = rollCount + 1;
   }
   return rollCount;
}

As for preparing for the loop, notice that the game's description says that the "game begins by rolling Die A, which establishes a positive Delta-value". Which means that the loop should be preceded by code that does a single roll of Die A. Indeed, we would not want execution to arrive at the loop without at least one die roll already having occurred, or else the Delta-value will be zero at that point, meaning that the loop will terminate after having iterated zero times! Which means that the game will end without a single die roll having occurred!

Refining our solution, we get this:

/* Plays the game described above using the two given six-sided dice. The
** value returned is the number of dice rolls that were made during the game.
*/
public static int diceGame(SixSidedDie dieA, SixSidedDie dieB) {

   int rollCount = 0; // # of dice rolls to have occurred
   int sumB = 0;      // sum(B)
   
   // Roll Die A to establish a positive Delta-value
   dieA.roll(); sumA = dieA.numPips();
   
   while (sumA - sumB != 0) {

      if (sumA - sumB < 0) {  // roll dieA if Delta is negative
         dieA.roll(); sumA = sumA + dieA.numPips();
      }
      else {  // roll dieB if Delta is positive
         dieB.roll(); sumB = sumB + dieB.numPips();
      } 

      rollCount = rollCount + 1;
   }
   return rollCount;
}

The above is a good solution, although a better one is obtained by recognizing that it is not really necessary to keep track of sum(A) and sum(B), as it suffices to keep track of only their difference, which is, of course, the Delta-value. Whenever Die A (respectively, Die B) is rolled, the Delta-value increases (respectively, decreases) by the result of that roll. Modifying our code accordingly, we get this better solution:

/* Plays the game described above using the two given six-sided dice. The
** value returned is the number of dice rolls that were made during the game.
*/
public static int diceGame(SixSidedDie dieA, SixSidedDie dieB) {

   int rollCount = 0; // # of dice rolls to have occurred
   int delta = 0;     // sum(A) - sum(B)

   // Roll Die A to establish a positive Delta-value
   dieA.roll(); delta = dieA.numPips();

   while (delta != 0) {

      if (delta < 0) {  // roll dieA if Delta is negative
         dieA.roll(); delta = delta + dieA.numPips();
      }
      else {  // roll dieB if Delta is positive
         dieB.roll(); delta = delta - dieB.numPips();
      } 

      rollCount = rollCount + 1;
   }
   return rollCount;
}

A (possibly) better solution is obtained by using a do-while-loop rather than a while-loop, making it unnecessary to perform a die roll prior to the first iteration:

/* Plays the game described above using the two given six-sided dice. The
** value returned is the number of dice rolls that were made during the game.
*/
public static int diceGame(SixSidedDie dieA, SixSidedDie dieB) {
   int rollCount = 0; // # of dice rolls to have occurred
   int delta = 0;     // sum(A) - sum(B)

   do {
      if (delta <= 0) {  // roll dieA if Delta is non-positive
         dieA.roll(); delta = delta + dieA.numPips();
      }
      else {  // roll dieB if Delta is positive
         dieB.roll(); delta = delta - dieB.numPips();
      } 
      rollCount = rollCount + 1;
   }
   while (delta != 0);

   return rollCount;
}

The subtlety here is that the first die roll will occur during the loop's first iteration and, to be in conformance with the game's rules, Die A must be chosen to be rolled. Ah, but the value of delta will be zero as the first iteration begins, which means that the condition delta == 0 should trigger a roll of Die A. This explains why the if-statement's condition is delta <= 0, in contrast to the delta < 0 condition in the previous solution.

Of course, no subsequent loop iteration can begin with delta == 0 holding, as in that case the loop would have terminated at the end of the most recent iteration.