/**
* BowlingGame lets you follow up the game of a bowling player, that
* means register throws and establish the frames and the scores of the
* game.
*
* Let's remind of the following :
*
* The bowling game is played on a lane with at the end 10 pins that
* the player should knock down with a ball that he throws and make roll
* on the lane.
*
* The placing of the ten pins make up what we call a frame. The player
* has two throws to try to knock down the 10 pins of a frame. If he
* knocks down all the ten pins by the first throw we say he makes a
* strike, and the second throw has no reason to be. If he has knocked
* down all the ten pins after the second throw, we say he makes a spare
*
* We will say that a frame is
*
* - current if next throw concerns some of its pins
* - finished if all its (one or two) throws are done (and so, except
* for the 10th, a next frame is current)
* - waiting if it is finished but not yet countable
* - ready if all the throws needed for the calculation of its score
* are done (a strike or a spare need further throws to calculate
* the score)
*
* A game is, for each player, made up of 10 frames. The total score is
* calculated as the sum of the (individual) score of each frame. This
* score is calculated as follows :
*
* - if the player makes a strike the score of the frame is 10 plus
* the number of pins (0 to 20) of the two next throws
* - else, if he makes a spare the score of the frame is 10 plus the
* number of pins (0 to 10) of the next throw
* - else the score of the frame is the number of pins (0 to 9)
* knocked down by the two throws of the frame.
*
* This counting may demand to replace the 10 pins one or two more
* times for the 10th frame (1 time in case of spare and two times in
* case of strike). We shall however agree that it's still the 10th
* frame that is current.
*/
public class BowlingGame {
final int maxFrames = 10+2; // 10 ordinary, 2 extra
final int maxThrows = 21;
Frame[] frames = new Frame[1 + maxFrames]; // 0 not used
int currentFrame = 1; // current index to frames
int[] pins = new int[1 + maxThrows]; // 0 not used
int currentThrow = 1; // current index to pins
/**
* Creates a BowlingGame ready to begin the counting.
* In particular : the current frame ({@link #currentFrame}) is the
* first one, the state of the frame ({@link #frameState}) is 0.
*/
public BowlingGame() {
for (int i = 1; i <= maxFrames; i++)
frames[i] = new Frame(pins);
}
/**
* Registers the pins knocked down by a throw.
* @param pins : the number of pins to register.
* @throws IndexOutOfBoundsException if the game is complete
* @throws IllegalArgumentException if on the first throw for a
* frame the number N = pins does not belong to [0, 10] or if
* on the second throw 'pins' does not belong to [0, 10 - N]
*/
public void add(int pins) {
if ((pins < 0) || (pins > 10))
throw new IllegalArgumentException("Pins out of bounds "+pins);
if (isReady(10))
throw new IndexOutOfBoundsException("Game over!");
this.pins[currentThrow] = pins;
for (int i = 0; i <= 2 ; i++) {
if (currentFrame - i >= 1) {
frames[currentFrame-i].update(currentThrow);
}
}
currentThrow++;
if (frames[currentFrame].finished()) {
currentFrame++;
}
}
/**
* Gives the cumulated score up to the given frame if the frame
* is ready.
* @return the score
* @throws IndexOutOfBoundsException if index don't belong to
* [1, a] where a is the number of frames that are ready
*/
public int cumulatedScore(int index) {
verifyBounds(index);
if (!isReady(index))
throw new IndexOutOfBoundsException("Frame not ready: " + index);
int sum = 0;
for ( ; index > 0; index--)
sum += frames[index].getScore();
return sum;
}
/**
* Gives the state of a frame.
* The state is
*
* - 0 if first throw is not yet done
* - 1 if strike and avaiting second throw
* - 2 if strike and avaiting third throw
* - 3 if ready after strike
* - 4 if first throw done (not strike) and before second
* - 5 if spare and avaiting third throw
* - 6 if ready after spare
* - 7 if ready after neither strike nor spare
*
* @param index : the index of the frame.
* @return the state of the frame
* @throws IndexOutOfBoundsException if the index of the frame does
* not belong to [1, 10]
*/
public int frameState(int index) {
verifyBounds(index);
return frames[index].getState();
}
/**
* Gives the number of pins of the n-th contributing throw of a frame.
* @param index : the index of the frame
* @param n : the rank >= 1 of the contributing throw
* @return the number of pins of the n-th contributing throw of the frame
* @throws IndexOutOfBoundsException if the corresponding throw does not exist
* or does not concern this frame
*/
public int throwForFrame(int index, int n) {
verifyBounds(index);
int f = frames[index].getThrow(n);
if (f == -1)
throw new IndexOutOfBoundsException("Index out of bounds "+index+" and "+n);
return f;
}
/**
A frame is ready if and only if all throws needed to complete its
score have been done.
*/
boolean isReady(int index) {
int s = frameState(index);
return (s == 3) || (s == 6) || (s == 7);
}
void verifyBounds(int index) {
if ((index < 1) || (index > 10))
throw new IndexOutOfBoundsException("Index out of bounds [1, 10]: "+index);
}
/**
The class Frame manages the state and the score of a given frame. It also
registers the index of the first throw of this frame in the array of
throws (pins).
The class is built as a state machine.
*/
class Frame {
int[] pins;
int state = 0;
int score = 0;
int first = -1;
public Frame(int[] t) {
pins = t;
}
/**
This method provokes the transitions of the state machine
*/
public void update(int index) {
switch (state) {
case 0 : first = index;
score = pins[index];
if (score == 10) state = 1;
else state = 4;
break;
case 1 : score += pins[index]; state = 2;
break;
case 2 : score += pins[index]; state = 3;
break;
case 4 : if (pins[first] + pins[index] > 10) throw new IllegalArgumentException(
"Sum of pins out of bounds: "+pins[first]+" "+pins[index]);
score += pins[index];
if (score == 10) state = 5;
else state = 7;
break;
case 5 : score += pins[index]; state = 6;
break;
default : ;
}
}
/**
Returns the number of pins of the n-th contributing throw.
Returns -1 if the corresponding throw does not concern
this frame (yet)
*/
public int getThrow(int n) {
if (n <= 0) return -1; // -1 means error
int p = pins[first - 1 + n];
switch (state) {
case 0: p = -1; break;
case 1:
case 4: if (n > 1) p = -1; break;
case 2:
case 5:
case 7: if (n > 2) p = -1; break;
case 3:
case 6: if (n > 3) p = -1; break;
}
return p;
}
/**
Returns the current state of this frame
@see BowlingGame.frameState
*/
public int getState() {
return state;
}
/**
Returns the current individual score of the frame.
*/
public int getScore() {
return score;
}
/**
The frame has finished when the one throw (strike) or two throws
(not strike) has been done
*/
public boolean finished() {
return (state != 0) && (state != 4);
}
}
}
Site updated 08 May, 2017 Remarks and questions? areusite at free dot fr