| 4 | 4 | // |
| 5 | 5 | |
| 6 | 6 | export class Bowling { |
| 7 | constructor() { |
| 8 | this.rolls = []; |
| 9 | this.currentRoll = 0; |
| 10 | } |
| 11 | |
| 7 | 12 | /** |
| 8 | 13 | * @param {number} pins |
| 9 | 14 | */ |
| 10 | 15 | roll(pins) { |
| 11 | | throw new Error('Remove this line and implement the function'); |
| 16 | // Edge Case: Negative roll is invalid |
| 17 | if (pins < 0) { |
| 18 | throw new Error('Negative roll is invalid'); |
| 19 | } |
| 20 | |
| 21 | // Edge Case: Pin count exceeds pins on the lane |
| 22 | if (pins > 10) { |
| 23 | throw new Error('Pin count exceeds pins on the lane'); |
| 24 | } |
| 25 | |
| 26 | // Edge Case: Cannot roll after game is over |
| 27 | if (this.isGameOver()) { |
| 28 | throw new Error('Cannot roll after game is over'); |
| 29 | } |
| 30 | |
| 31 | // Edge Case: Pin count exceeds pins on the lane (in frame) |
| 32 | // Need to check if we're in a regular frame (not 10th frame) |
| 33 | if (!this.isInTenthFrame() && this.currentRoll > 0) { |
| 34 | const previousRoll = this.rolls[this.currentRoll - 1]; |
| 35 | // Check if we're on the second roll of a frame |
| 36 | const isSecondRoll = this.isSecondRollOfFrame(); |
| 37 | |
| 38 | if (isSecondRoll && previousRoll + pins > 10) { |
| 39 | throw new Error('Pin count exceeds pins on the lane'); |
| 40 | } |
| 41 | } |
| 42 | |
| 43 | this.rolls[this.currentRoll++] = pins; |
| 12 | 44 | } |
| 13 | 45 | |
| 14 | 46 | /** |
| 15 | 47 | * @returns {number} |
| 16 | 48 | */ |
| 17 | 49 | score() { |
| 18 | | throw new Error('Remove this line and implement the function'); |
| 50 | // Edge Case: Score cannot be taken until the end of the game |
| 51 | if (!this.isGameOver()) { |
| 52 | throw new Error('Score cannot be taken until the end of the game'); |
| 53 | } |
| 54 | |
| 55 | let score = 0; |
| 56 | let rollIndex = 0; |
| 57 | |
| 58 | for (let frame = 0; frame < 10; frame++) { |
| 59 | if (this.isStrike(rollIndex)) { |
| 60 | score += 10 + this.strikeBonus(rollIndex); |
| 61 | rollIndex++; |
| 62 | } else if (this.isSpare(rollIndex)) { |
| 63 | score += 10 + this.spareBonus(rollIndex); |
| 64 | rollIndex += 2; |
| 65 | } else { |
| 66 | score += this.sumOfBallsInFrame(rollIndex); |
| 67 | rollIndex += 2; |
| 68 | } |
| 69 | } |
| 70 | |
| 71 | return score; |
| 72 | } |
| 73 | |
| 74 | isGameOver() { |
| 75 | let frameIndex = 0; |
| 76 | let rollIndex = 0; |
| 77 | |
| 78 | // Count frames through first 9 frames |
| 79 | for (let frame = 0; frame < 9; frame++) { |
| 80 | if (rollIndex >= this.currentRoll) { |
| 81 | return false; // Not enough rolls yet |
| 82 | } |
| 83 | |
| 84 | if (this.rolls[rollIndex] === 10) { |
| 85 | // Strike |
| 86 | rollIndex += 1; |
| 87 | } else { |
| 88 | // Regular frame or spare |
| 89 | if (rollIndex + 1 >= this.currentRoll) { |
| 90 | return false; // Not enough rolls for this frame |
| 91 | } |
| 92 | rollIndex += 2; |
| 93 | } |
| 94 | } |
| 95 | |
| 96 | // Check if we have enough rolls for 10th frame |
| 97 | if (rollIndex >= this.currentRoll) { |
| 98 | return false; // No 10th frame yet |
| 99 | } |
| 100 | |
| 101 | // Handle 10th frame |
| 102 | const firstRoll = this.rolls[rollIndex]; |
| 103 | |
| 104 | if (firstRoll === 10) { |
| 105 | // Strike in 10th frame - need 2 more rolls |
| 106 | return rollIndex + 3 <= this.currentRoll; |
| 107 | } else if (rollIndex + 1 < this.currentRoll && firstRoll + this.rolls[rollIndex + 1] === 10) { |
| 108 | // Spare in 10th frame - need 1 more roll |
| 109 | return rollIndex + 3 <= this.currentRoll; |
| 110 | } else { |
| 111 | // Open frame in 10th - need exactly 2 rolls |
| 112 | return rollIndex + 2 <= this.currentRoll; |
| 113 | } |
| 114 | } |
| 115 | |
| 116 | isStrike(rollIndex) { |
| 117 | return this.rolls[rollIndex] === 10; |
| 19 | 118 | } |
| 119 | |
| 120 | isSpare(rollIndex) { |
| 121 | return this.rolls[rollIndex] + this.rolls[rollIndex + 1] === 10; |
| 122 | } |
| 123 | |
| 124 | sumOfBallsInFrame(rollIndex) { |
| 125 | return this.rolls[rollIndex] + this.rolls[rollIndex + 1]; |
| 126 | } |
| 127 | |
| 128 | spareBonus(rollIndex) { |
| 129 | return this.rolls[rollIndex + 2]; |
| 130 | } |
| 131 | |
| 132 | strikeBonus(rollIndex) { |
| 133 | return this.rolls[rollIndex + 1] + this.rolls[rollIndex + 2]; |
| 134 | } |
| 135 | |
| 136 | isInTenthFrame() { |
| 137 | let frameCount = 0; |
| 138 | let rollIndex = 0; |
| 139 | |
| 140 | // Count frames up to current roll |
| 141 | while (rollIndex < this.currentRoll && frameCount < 10) { |
| 142 | if (this.rolls[rollIndex] === 10) { |
| 143 | // Strike |
| 144 | rollIndex += 1; |
| 145 | frameCount++; |
| 146 | } else { |
| 147 | // Regular frame or spare |
| 148 | rollIndex += 2; |
| 149 | frameCount++; |
| 150 | } |
| 151 | } |
| 152 | |
| 153 | return frameCount >= 9; |
| 154 | } |
| 155 | |
| 156 | isSecondRollOfFrame() { |
| 157 | let frameCount = 0; |
| 158 | let rollIndex = 0; |
| 159 | |
| 160 | // Count frames up to current roll |
| 161 | while (rollIndex < this.currentRoll && frameCount < 10) { |
| 162 | if (this.rolls[rollIndex] === 10) { |
| 163 | // Strike - only one roll in this frame |
| 164 | rollIndex += 1; |
| 165 | frameCount++; |
| 166 | } else { |
| 167 | // Regular frame or spare - two rolls |
| 168 | if (rollIndex === this.currentRoll - 1) { |
| 169 | return true; // We're on the second roll of this frame |
| 170 | } |
| 171 | rollIndex += 2; |
| 172 | frameCount++; |
| 173 | } |
| 174 | } |
| 175 | |
| 176 | return false; |
| 177 | } |
| 178 | |
| 179 | // Handled Edge Cases: Negative roll is invalid, Pin count exceeds pins on the lane, Cannot roll after game is over, Pin count exceeds pins on the lane (in frame), Score cannot be taken until the end of the game |
| 20 | 180 | } |