Not sure if will help assist anyone but I thought I’d share my heavily-annotated solution in just under 60 lines with comments removed. I think of myself as a pretty experienced game developer outside of Unity but I’ve honestly always struggled with coding a Bowling scorer, so this was a refreshing 3 hours!
Anyway, this solution is more or less a C# version of what I’d do in my head when totaling bowling scores. No fancy tricks here!
public static List<int> ScoreFrames(List<int> pinFalls) {
// Track list of frame scores
List<int> scores = new List<int>();
// The current roll number
int rollNumber = 1;
// Temporary running total
int runningTotal = 0;
// Temporary bonus total
int bonusTotal = 0;
// Temporarily remember the previously "bonused" ball
int lastScoredBall = 0;
// Bonus balls (used for calculating strike/spare score)
int bonusBalls = 0;
// Loop through each pin fall count
for (int i = 0; i < pinFalls.Count; i++) {
// Extract total pins value to mimic foreach
int total = pinFalls[i];
if (bonusBalls > 0) {
// This is a continuation of a bonus ball aggregation due to strikes/spares. Add this current ball to bonus total
bonusTotal += total;
// Decrease the bonus balls count
bonusBalls--;
// If there are no bonus balls left
if (bonusBalls <= 0) {
// Add score to bonus total
scores.Add(bonusTotal);
bonusTotal = 0;
// Rewind back to lastScoredFrame
i = lastScoredBall;
}
} else {
// Begin normal scoring logic
// Increment running total
runningTotal += total;
// If is the final frame
if (scores.Count >= 9) {
// Only add score once we reach the final bowl
// i.e. If last bowl, and 2nd roll or greater, and the first roll WASNT a strike
if (i >= pinFalls.Count - 1 && rollNumber > 1 && !(pinFalls[i - 1] == 10 && rollNumber < 3)) {
scores.Add(runningTotal);
}
// Not required but for consistency
rollNumber++;
} else if (rollNumber >= 2 || runningTotal >= 10) {
// If is the second roll, or a strike/spare and is NOT the final frame
if (runningTotal >= 10) {
// If newly started strike/spare (First roll = strike, second roll = spare)
// Note: Strikes count the next two balls worth of points, while spares count the next one
bonusBalls = (rollNumber == 1) ? 2 : 1;
bonusTotal = 10;
lastScoredBall = i;
// Reset the roll number and running total
runningTotal = 0;
rollNumber = 1;
} else {
// This is just a normal completed frame, add to running total and reset roll count
scores.Add(runningTotal);
lastScoredBall = i;
runningTotal = 0;
rollNumber = 1;
}
} else {
// Normal roll, increment roll number
rollNumber++;
}
}
}
if (scores.Count > 10) {
throw new UnityException("Error, scores should not be beyond 10");
}
return scores;
}
I also added a few additional bonus tests that weren’t initially covered by my program:
[Test]
public void T22IncompleteLastFrame () {
int[] rolls = { 1,1, 1,1, 1,1, 1,1, 1,1, 1,1, 1,1, 1,1, 1,1, 3};
int[] totals = { 2, 4, 6, 8, 10, 12, 14, 16, 18 };
Assert.AreEqual (totals.ToList(), ScoreMaster.ScoreCumulative (rolls.ToList()));
}
[Test]
public void T23IncompleteLastFrameStrike () {
int[] rolls = { 1,1, 1,1, 1,1, 1,1, 1,1, 1,1, 1,1, 1,1, 1,1, 10, 5};
int[] totals = { 2, 4, 6, 8, 10, 12, 14, 16, 18 };
Assert.AreEqual (totals.ToList(), ScoreMaster.ScoreCumulative (rolls.ToList()));
}
[Test]
public void T24TenSpare () {
int[] rolls = { 0, 10, 9, 0};
int[] totals = {19, 28 };
Assert.AreEqual (totals.ToList(), ScoreMaster.ScoreCumulative (rolls.ToList()));
}