I wanted to practice my programming skill and I started solving exercises from kata-logs site. The exercise:
The game consists of 10 frames. In each frame the player has two rolls to knock down 10 pins. The score for the frame is the total number of pins knocked down, plus bonuses for strikes and spares.
A spare is when the player knocks down all 10 pins in two rolls. The bonus for that frame is the number of pins knocked down by the next roll.
A strike is when the player knocks down all 10 pins on his first roll. The frame is then completed with a single roll. The bonus for that frame is the value of the next two rolls.
In the tenth frame a player who rolls a spare or strike is allowed to roll the extra balls to complete the frame. However no more than three balls can be rolled in tenth frame.
My solution:
class Frame {
private Optional<Integer> firstRole = Optional.empty();
private Optional<Integer> secondRole = Optional.empty();
private Optional<Frame> next = Optional.empty();
public void knock(int knockedPins) {
if (secondRole.isPresent() && firstRole.isPresent()) {
if (!next.isPresent()) {
Frame nextFrame = new Frame();
nextFrame.knock(knockedPins);
next = Optional.of(nextFrame);
return;
}
next.ifPresent(frame -> frame.knock(knockedPins));
return;
}
if (firstRole.isPresent()) {
secondRole = Optional.of(knockedPins);
return;
}
if (knockedPins == 10) {
firstRole = Optional.of(knockedPins);
secondRole = Optional.of(0);
next = Optional.of(new Frame());
return;
}
firstRole = Optional.of(knockedPins);
}
public int finishedFramesCount() {
if (!next.isPresent()) {
if (frameFinished()) {
return 1;
}
return 0;
} else {
return 1 + next.get().finishedFramesCount();
}
}
public int points() {
int knockedPins = firstRole.orElse(0) + secondRole.orElse(0);
int bonusPointsForSpare = isSpare() ? next.map(Frame::firstRoleScore).orElse(0) : 0;
int bonusPointsForStrike = isStrike() ? next.map(frame -> frame.firstRoleScore() + frame.secondRoleScore()).orElse(0) : 0;
int totalResult = knockedPins + bonusPointsForStrike + bonusPointsForSpare;
return next.map(frame -> totalResult + frame.points()).orElse(totalResult);
}
public Frame lastFrame() {
if (!next.isPresent()) {
return this;
} else {
return next.get().lastFrame();
}
}
public boolean isSpare() {
return secondRole.isPresent() && secondRole.get() > 0 && firstRole.get() + secondRole.get() == 10;
}
public boolean isStrike() {
return firstRole.isPresent() && firstRole.get() == 10;
}
private boolean frameFinished() {
return firstRole.isPresent() && secondRole.isPresent();
}
private int firstRoleScore() {
return firstRole.orElse(0);
}
private int secondRoleScore() {
return secondRole.orElse(0);
}
}
class StandardBowlingGame implements BowlingGame {
private final Frame frame;
private int bonusRole;
public StandardBowlingGame(Frame frame) {
this.frame = frame;
}
@Override
public void roll(int knockedPins) {
if (frame.finishedFramesCount() == 10) {
if (frame.lastFrame().isStrike() || frame.lastFrame().isSpare()) {
bonusRole = knockedPins;
return;
}
throw new IllegalStateException("Game is over");
}
frame.knock(knockedPins);
}
@Override
public int score() {
return frame.points() + bonusRole;
}
}
BowlingGame
interface? is it given as part of the question or is it your design? \$\endgroup\$