The Challenge

Bowling scoring looks simple on the surface, but the rules for strikes, spares, and especially the 10th frame make it a surprisingly fun algorithmic problem.

A perfect game is 300 points across 10 frames. Each frame allows up to 2 rolls (3 in the 10th frame), and bonus points cascade forward based on strikes and spares.

Scoring Rules

  • Normal frame: Sum of two rolls
  • Spare (knock all 10 down in 2 rolls): 10 + next 1 roll as bonus
  • Strike (knock all 10 down in 1 roll): 10 + next 2 rolls as bonus
  • 10th frame: If you get a strike or spare, you get bonus rolls (up to 3 total)

The Core Algorithm

function calculateScore(rolls) {
  let score = 0;
  let rollIndex = 0;

  for (let frame = 0; frame < 10; frame++) {
    if (isStrike(rolls, rollIndex)) {
      score += 10 + strikeBonus(rolls, rollIndex);
      rollIndex += 1;
    } else if (isSpare(rolls, rollIndex)) {
      score += 10 + spareBonus(rolls, rollIndex);
      rollIndex += 2;
    } else {
      score += rolls[rollIndex] + rolls[rollIndex + 1];
      rollIndex += 2;
    }
  }

  return score;
}

function isStrike(rolls, i) { return rolls[i] === 10; }
function isSpare(rolls, i) { return rolls[i] + rolls[i + 1] === 10; }
function strikeBonus(rolls, i) { return rolls[i + 1] + rolls[i + 2]; }
function spareBonus(rolls, i) { return rolls[i + 2]; }

What I Learned

The key insight is to track by roll index rather than frame number. The frame boundaries shift when strikes occur (only 1 roll per frame instead of 2). This makes the algorithm clean and avoids special-casing.

The project uses Node.js for the backend logic and Bootstrap for a simple UI that updates scores in real-time as you enter each roll.

Check out the source code on GitHub.