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).
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 > } |
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.